AI实践哪家强?来 AICon, 解锁技术前沿,探寻产业新机! 了解详情
写点什么

如果 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:291705

评论

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

【数据结构与算法】8道链表面试真题超详剖析,带你领略算法思想【附思路、动图、源码】

Dream-Y.ocean

链表 数据结构与算法 9月月更

【文本检测与识别-白皮书】第二章:文本检测与识别技术发展历程

合合技术团队

人工智能 文字识别 OCR

跨模态学习能力再升级,EasyNLP电商文图检索效果刷新SOTA

阿里云大数据AI技术

深度学习 PyTorch 图文检索 企业号九月金秋榜

Java实现SM3加密和验证

共饮一杯无

Java 9月月更 SM3

保障系统安全

源字节1号

软件开发 信息安全 系统安全

如何实现数据库读一致性

京东科技开发者

数据库 读写锁 事务 MVCC 一致性

三三复制公排互助系统开发技术详情

开发微hkkf5566

用区块链思维让可信数据流动起来

旺链科技

区块链 产业区块链 供应链金融 企业号九月金秋榜

现代化车企——零束科技DevOps研发数字化体系建设实践

嘉为蓝鲸

#DevOps #研发 #汽车 #科技

ShareSDK HarmonyOS集成指南

MobTech袤博科技

sdk HarmonyOS

阿里P8手写Spring Cloud Alibaba实战学习手册,架构师养成必备

Geek_0c76c3

Java 数据库 开源 程序员 架构

被裁后半月面试8家公司无果,凭借这份Java面试指南成功入职阿里

Geek_0c76c3

Java spring 程序员 架构 面经

通过Thread Pool Executor类解析线程池执行任务的核心流程

华为云开发者联盟

后端 开发 企业号九月金秋榜

新来个阿里 P7,仅花 2 小时,撸出一个多线程永动任务,看完直接跪了,真牛逼!

程序知音

极客时间—架构实战营—第九期—模块一作业

阿梁

从 DFS 到回溯法,再看 N 皇后问题

掘金安东尼

前端 9月月更

什么是架构,什么是架构师?

源字节1号

软件开发 软件架构

Dubbo 泛化调用在vivo统一配置系统的应用

vivo互联网技术

dubbo 中间件 配置管理

YYEVA动效播放器--动态元素完美呈现新方案

百度Geek说

移动端 企业号九月金秋榜 动画特效 AE插件

想要优化K8S集群管理?Cluster API帮你忙 | K8S Internals系列第5期

BoCloud博云

云原生 容器云 K8s 多集群管理

如何解决 Angular custom library module 在 ng build 时无法被识别的错误

汪子熙

typescript 前端开发 angular web开发 9月月更

Seal 软件供应链防火墙 v0.2 发布,提供依赖项全局洞察

SEAL安全

DevSecOps 开源安全 软件供应链 软件供应链安全 软件供应链防火墙

小程序化:系统集成行业降本增效的破局思考

Speedoooo

小程序 数字化转型 小程序容器 生态平台

HowTo:Pipy 如何统计关键指标

Flomesh

Service Mesh 服务网格

关于加密数据的模糊查询

知识浅谈

9月月更 加密数据

开发者有话说|用成长治愈所有迷茫

法医

程序员 个人成长

苏州市大数据集团成立!博云成为首批数商代表

BoCloud博云

云计算 数字化 云平台

docker与gosu

程序员欣宸

Docker 9月月更 gosu

大数据ELK(五):Elasticsearch中的核心概念

Lansonli

ELK 9月月更

Ventana Research|是时候把指标中台纳入企业数据架构了!

Kyligence

数据架构 数据管理 指标中台

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