【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

如果 ReasonML 没有 JavaScript 的那些怪癖,你该不该试试它?

  • 2019-12-27
  • 本文字数:5425 字

    阅读完需:约 18 分钟

如果 ReasonML 没有 JavaScript 的那些怪癖,你该不该试试它?

TypeScript 和 ReasonML 都声称自己为 Web 开发人员提供了可编译为 JavaScript 的静态类型语言,那么它们之间的差别是什么?ReasonML 能带来 TypeScript 想要做到的一切(甚至更多),但是前者没有那些 JavaScript 怪癖。这样的话,你是否应该试试它呢?


TypeScript 是 JavaScript 的超集,这既是它的最佳特性,也是它最大的缺陷。虽说与 JavaScript 的相似性给了人熟悉的感觉,但这意味着我们所喜爱和反感的所有 JavaScript 怪癖都在 TypeScript 里重现了。TS 只不过是在 JavaScript 之上添加了类型,然后就差不多完事了。


ReasonML 提供的是一种完全不同但让人感觉很熟悉的语言。这是否意味着 JavaScript /TypeScript 开发人员会很难学习这个新语言?我们就来看看吧。

声明一个变量

让我们从变量声明开始:


ReasonMLlet a = "Hi";TypeScriptconst a = "Hi"
复制代码


在 ReasonML 中,我们使用 let 关键字声明一个变量。没有 const,默认情况下 let 是不可变的。


在这种情况下,两种语言都可以推断出 a 的类型。

函数

TypeScript


let sum = (a: number, b: number) => a + b
复制代码


ReasonML


let sum = (a,b) => a + b;
复制代码


尽管我没有手动编写任何类型,但这个函数的参数还是类型化了。为什么我们用不着在 ReasonML 中编写类型呢?因为强大的类型系统可以进行出色的类型推断。这意味着编译器可以在不需要你帮助的情况下推断出类型。ReasonML 中的 (+) 运算符仅适用于整数——a 和 b 只能是这种类型,因此我们不必编写它们。但如果需要,你随时都可以编写类型:


ReasonML


let sum = (a: int, b: int) => a + b;
复制代码

接口,记录

TypeScriptinterface Product {  name: string  id: number}
复制代码


ReasonML 中最接近接口(Interface)的是记录(Record,https://reasonml.github.io/docs/en/record)。


ReasonML


type product = {  name: string,  id: int,};
复制代码


记录就像 TypeScript 对象一样,但前者是不可变的,固定的,并且类型更严格。下面我们在某些函数中使用定义的结构:


ReasonML


let formatName = product => "Name: "++product.name;
复制代码


TypeScript


const formatName = (product: Product) => "Name: " + product.name
复制代码


同样,我们不需要注释类型!在这个函数中我们有一个参数 product,其属性 name 为字符串类型。ReasonML 编译器可以根据使用情况猜测该变量的类型。因为只有 product 这个类型具有字符串类型的 name 属性,编译器会自动推断出它的类型。

更新记录

let updateName = (product, name) => { ...product, name };
复制代码


const updateName = (product: Product, name: string) => ({ ...product, name })
复制代码


ReasonML 支持展开运算符,并像 TypeScript 一样对名称和值做类型双关。


看看 ReasonML 生成了什么样的 JavaScript 吧,这也很有趣:


function updateName(product, name) {  return [name, product[1]]}
复制代码


ReasonML 中的记录表示为数组。如果人类可以像编译器一样记住每种类型的每个属性的索引,则生成的代码看起来就会像人类编写的那样。

Reducer 示例

我认为这就是 ReasonML 真正闪耀的地方。我们来比较一下相同的 Reducer 实现:


在 TypeScript 中(遵循这份指南:https://redux.js.org/recipes/usage-with-typescript)


interface State {  movies: string[]}
const defaultState: State = { movies: [],}
export const ADD_MOVIE = "ADD_MOVIE"export const REMOVE_MOVIE = "REMOVE_MOVIE"export const RESET = "RESET"
interface AddMovieAction { type: typeof ADD_MOVIE payload: string}
interface RemoveMovieAction { type: typeof REMOVE_MOVIE payload: string}
interface ResetAction { type: typeof RESET}
type ActionTypes = AddMovieAction | RemoveMovieAction | ResetAction
export function addMovie(movie: string): ActionTypes { return { type: ADD_MOVIE, payload: movie, }}
export function removeMovie(movie: string): ActionTypes { return { type: REMOVE_MOVIE, payload: movie, }}
export function reset(): ActionTypes { return { type: RESET, }}
const reducer = (state: State, action: Action) => { switch (action.type) { case ADD_MOVIE: return { movies: [movie, ...state.movies] } case REMOVE_MOVIE: return { movies: state.movie.filter(m => m !== movie) } case RESET: return defaultState default: return state }}
复制代码


没什么特别的,我们声明了状态界面、默认状态、动作、动作创建者以及最后的 Reducer。


在 ReasonML 中也是一样:


type state = {  movies: list(string)};
type action = | AddMovie(string) | RemoveMovie(string) | Reset
let defaultState = { movies: [] }
let reducer = (state) => fun | AddMovie(movie) => { movies: [movie, ...state.movies] } | RemoveMovie(movie) => { movies: state.movies |> List.filter(m => m !== movie) } | Reset => defaultState;
/* No need for additional functions! */let someAction = AddMovie("The End of Evangelion")
复制代码


对,就这些。


让我们看看这里发生了什么。


首先,有一个状态类型声明。


之后是动作 Variant(https://blog.dubenko.dev/typescript-vs-reason/#https://reasonml.github.io/docs/en/variant)类型:


type action =  | AddMovie(string)  | RemoveMovie(string)  | Reset
复制代码


这意味着具有类型 action 的任何变量都可以具有以下值之一:Reset、带有一些字符串值的 AddMovie 和带有一些字符串值的 RemoveMovie。


ReasonML 中的 Variant 是一项非常强大的功能,可让我们以非常简洁的方式定义可以包含值 A 或 B 的类型。是的,TypeScript 有联合类型,但它没有深入集成到语言中,因为 TypeScript 的类型是给 JavaScript 打的补丁;而 Variant 是 ReasonML 语言的重要组成部分,并且与模式匹配等其他语言功能紧密相连。


说到模式匹配,我们来看一下 Reducer。


let reducer = (state) => fun  | AddMovie(movie) => { movies: [movie, ...state.movies] }  | RemoveMovie(movie) => { movies: state.movies |> List.filter(m => m !== movie) }  | Reset => defaultState;
复制代码


我们在这里看到的是一个函数,该函数接受状态作为第一个参数,然后将第二个参数与可能的值匹配。


我们也可以这样写这个函数:


let reducer = (state, action) => {  switch(action) {  | AddMovie(movie) => { movies: [movie, ...state.movies] }  | RemoveMovie(movie) => { movies: state.movies |> List.filter(m => m !== movie) }  | Reset => defaultState;  }}
复制代码


由于匹配参数是 ReasonML 中的常见模式,因此这类函数以较短的格式编写,如前面的代码片段中所示。{ movies: [movie, …state.movies] }这部分看起来与 TypeScript 中的一样,但是这里发生的事情并不相同!在 ReasonML 中,[1,2,3] 不是数组,而是不可变的列表。可以想象它是在语言本身内置的 Immutable.js。在这一部分中我们利用了一个事实,即 Append 操作在 ReasonML 列表中的时间是恒定的!如果你之前用的是 JavaScript 或 TypeScript,你可能会随手写下这种代码,无需太多顾虑,并且可以免费获得性能提升。


现在,让我们看看向 Reducer 添加新动作的操作。在 TypeScript 中是怎么做的呢?首先你要在类型定义中添加一个新动作,然后以动作创建者的形式编写一些样板,当然不要忘了在 Reducer 中实际处理这种情况,一般这一步都容易被忽略。


在 ReasonML 中,第一步是完全相同的,但是后面就都不一样了。在将新动作添加到类型定义后单击保存时,编译器会带你在代码库中慢慢挪动,处理对应的情况。


你会看到这样的警告:


Warning 8: this pattern-matching is not exhaustive.Here is an example of a case that is not matched:Sort
复制代码


这是很好的开发体验。它会指出你要处理新动作的确切位置,并且还会准确告诉你缺少哪种情况。

Null、undefined vs Option

在 TypeScript 中我们需要承受 JavaScript 留下来的负担,也就是用 null 和 undefined 来表示几乎一模一样的事物——毫无意义。


在 ReasonML 中没有这种东西,只有 Option 类型。


ReasonML


type option('a) =  | Some('a)  | None;
复制代码


这是一个大家很熟悉的 Variant 类型。但是它也有一个类型参数’a。这很像其他语言中的泛型,比如说 Option。


来对比更多的代码看看:


interface User {  phone?: number}
interface Form { user?: User}
function getPhone(form: Form): number | undefined { if (form.user === undefined) { return undefined } if (form.user.phone === undefined) { return undefined } return form.user.phone}
复制代码


访问可以为空的属性是最简单的情况之一,闭着眼也能写出来。在 TypeScript 中,我们可以启用严格的 null 检查然后手动检查 undefined 的值来纠正它。


open Belt.Option;
type user = { phone: option(int)};
type form = { user: option(user)};
let getPhone = form => form.user->flatMap(u => u.phone);
复制代码


在 ReasonML 中,我们可以使用内置的 option 类型和 Belt 标准库中的辅助函数,然后就能以标准化方式处理可能为空的值。

带标签的参数

我觉得所有人都会认为带标签的参数功能真是太棒了。可能每个人都必须在某个时候查询函数参数的顺序或含义。不幸的是,TypeScript 中没有带标签的参数。


TypeScript


function makeShadow(x: number, y: number, spread: number, color: string) {  return 0}const shadow = makeShadow(10, 10, 5, "black") /* meh */
复制代码


在 ReasonML 中,你在参数名称前加一个~ 字符,然后它就被标记了。


ReasonML


let makeShadow = (~x: int, ~y: int, ~spread: int, ~color: string) => {  0;}
let shadow = makeShadow(~spread=5, ~x=10, ~y=10, ~color="black")
复制代码


是的,你可以在 TypeScript 中尝试使用对象作为参数来模拟这种做法,但是随后你需要在每个函数调用时分配一个对象 :/


TypeScript


function makeShadow(args: {  x: number  y: number  spread: number  color: number}) {  return 0}
const shadow = makeShadow({ x: 10, y: 10, spread: 5, color: "black" })
复制代码

模块系统

在 TypeScript 中,我们显式导出和导入文件之间的所有内容。


Hello.ts


export const test = "Hello"
复制代码


import { test } from "./Hello.ts"
console.log(test)
复制代码


在 ReasonML 中,每个文件都是一个带有该文件名的模块。


Hello.re


let test = "Hello";
复制代码


Js.log(Hello.test);
复制代码


你可以 open 模块,使内容可用而无需模块名称前缀:


open Hello;
Js.log(test);
复制代码

编译速度

为了对比编译速度,我们来编译 TodoMVC,因为其实现和项目大小都是容易对比的。我们正在测试的是将代码转换为 JavaScript 所花费的时间。没有打包,压缩等操作。[注 1]


TypeScript + React.js


$ time tsc -p jstsc -p js 6.18s user 0.24s system 115% cpu 5.572 total
复制代码


6.18 秒


ReasonML + ReasonReact


$ bsb -clean-world$ time bsb -make-world[18/18] Building src/ReactDOMRe.mlast.d[9/9] Building src/ReactDOMRe.cmj[6/6] Building src/Fetch.mlast.d[3/3] Building src/bs_fetch.cmj[12/12] Building src/Json_encode.mlast.d[6/6] Building src/Json.cmj[7/7] Building src/todomvc/App.mlast.d[3/3] Building src/todomvc/App-ReasonReactExample.cmjbsb -make-world 0.96s user 0.73s system 161% cpu 1.049 total
复制代码


0.96 秒


现在这还包括编译 ReasonML 依赖项的时间,我们还可以测试只编译项目文件的时间:


ReasonML + ReasonReact, only src/


$ bsb -clean$ time bsb -make-worldninja: no work to do.ninja: no work to do.ninja: no work to do.[7/7] Building src/todomvc/App.mlast.d[3/3] Building src/todomvc/App-ReasonReactExample.cmjbsb -make-world 0.33s user 0.27s system 117% cpu 0.512 total
复制代码


0.33 秒


竟然有这么快!


BuckleScript 将安装时、构建时和运行时的性能视为一项重要功能


这是从 BuckleScript 文档中引用的。BuckleScript 是将 ReasonML 转换为 JavaScript 的工具。

目前 TypeScript 胜过 ReasonML 的地方

关于 ReasonML 的内容并不是都那么美好。它是一种相当新的语言……嗯,实际上它不是基于 OCaml 的,后者已经相当老了;但重点在于互联网上的资源仍然不是特别多。与 ReasonML 相比,用谷歌搜索 TypeScript 的问题更容易获得答案。


DefinitelyTyped 类型实在太多了,ReasonML 想要追上来还有很长的路要走。

结语

对于前端开发人员来说,ReasonML 语法应该让人觉得很熟悉,这意味着学习曲线的起点并不那么陡峭(但仍然比 TypeScript 要陡)。ReasonML 充分利用了其他语言和工具(例如 Immutable.js 和 eslint)中最好的东西,并将其带入了语言级别。它并没有试着成为一种完全纯粹的编程语言,你需要的话可以随时退回到突变和命令式编程。它非常快,它的速度是提升开发体验的重点所在。ReasonML 能带来 TypeScript 想要做到的一切(甚至更多),但是前者没有那些 JavaScript 怪癖。你应该试试它!

注释

[1] 编译时间测试平台是一部 MacBook Pro 2015,处理器是 Intel Core i5-5287U @2.90GHZ


TypeScript + React.js 源码(https://github.com/tastejs/todomvc/tree/master/examples/typescript-react)


ReasonML + ReasonReact 源码(https://github.com/reasonml-community/reason-react-example/tree/master/src/todomvc)


原文链接:


https://blog.dubenko.dev/typescript-vs-reason/


2019-12-27 16:291470

评论

发布
暂无评论
发现更多内容

利用jd.item_get API获取商品信息,打造可读性强的商品介绍

技术冰糖葫芦

API 接口 API 文档 pinduoduo API

仓储管理解决方案:混合低代码与定制开发,实现灵活性与效率的完美结合

天津汇柏科技有限公司

低代码开发 软件开发定制 仓储系统

MES管理系统与其他系统的集成

万界星空科技

数字化转型 系统集成 mes 万界星空科技

Apifox 最新功能更新:OAuth 2.0 鉴权、多分支Mock及更多

Apifox

程序员 前端 后端 Apifox

卡片式笔记系统BYEAP介绍

zhumingwu

玩转云端| 拥有HBlock这项“存储盘活绝技”,数据中心也能“热辣瘦身”!

天翼云开发者社区

云计算 存储 数据中心 集群

金三银四 面试准备 数据结构面试题,编程与算法经典面试题

测吧(北京)科技有限公司

测试

金三银四 面试准备 数据结构面试题,编程与算法经典面试题

测试人

面试 软件测试 求职

Ceph的crush算法与一致性hash对比介绍

天翼云开发者社区

云计算 存储 云服务

认识一下JavaScrip中的元编程

华为云开发者联盟

开发 华为云 华为云开发者联盟 企业号2024年4月PK榜

软件测试学习笔记丨Selenium网页frame

测试人

软件测试

驼峰与下划线在python中转化

麦兜

刘强东“分身”直播首秀带火 AI 数字人直播,青否数字人厂商备受关注!

青否数字人

数字人

Prompt工程师压箱底绝活——Prompt的基本组成部分、格式化输出与应用构建

百度Geek说

企业号 4 月 PK 榜 Prompt

lerna-lite 轻量化 monorepo 管理利器

小鑫同学

青否数字人直播带货源码有哪些功能?

青否数字人

数字人

Anon Network:基于 Ator Protocol 的 DePIN 匿名互联网

大瞿科技

如何通过二维码追踪物品流转情况?添加这个组件即可

草料二维码

二维码 草料二维码

MES系统解决方案,MES系统主要能解决什么问题

万界星空科技

数字化 生产管理系统 mes 万界星空科技 车间管理

Stable Diffusion中的常用术语解析

程序那些事

人工智能 程序那些事 openai

文心中国行走进成都!4月24日一起把握大模型时代的产业新机遇

飞桨PaddlePaddle

百度 BAIDU 百度飞桨 文心大模型 文心中国行

出海不出局 | 小游戏引爆高线市场,新竞争态势下的应用出海攻略

融云 RongCloud

面试不会算法和数据结构,经典面试题讲解来了!

霍格沃兹测试开发学社

Anon Network:基于 Ator Protocol 的 DePIN 匿名互联网

长安区块链

2024最新风口项目 巨量 腾讯 百度 快手 小红书广告代理 信息流广告代理

陈老师讲创业

QCN9274 vs QCN9224 chip performance comparison-wireless communication innovation

wifi6-yiyi

5G qcn9274 QCN9224

LLaMA 2语言大模型的微调策略:LoRA与全参数选择

百度开发者中心

人工智能 大模型 llama2

GLM国产大模型训练加速:高效性能与成本优化的实践

百度开发者中心

人工智能 深度学习 大模型

第六届科技无障碍发展大会(2024TADC)

信息无障碍研究会

Giants Planet 宣布推出符文,建立在坚实价值的基础上

西柚子

信创国产化替换势在必行,“重复造车轮”应避免

FinFish

国产化 小程序容器 信创化软件 信创化

如果 ReasonML 没有 JavaScript 的那些怪癖,你该不该试试它?_文化 & 方法_Oleksandr Dubenko_InfoQ精选文章