最新发布《数智时代的AI人才粮仓模型解读白皮书(2024版)》,立即领取! 了解详情
写点什么

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

评论

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

得帆云iPaaS是主数据必备工具

得帆信息

集成

马蹄链阿凡达项目怎么玩的?合约源码公开分析

加密先生

云原生数据库 | Data Infra 第 10 期

Databend

Flink CDC 专题首发|每天 10 分钟,解锁新一代数据集成框架

Apache Flink

大数据 flink 实时计算

微前端架构的业务价值:实现独立部署、快速迭代和按需加载

FinFish

微前端 小程序容器 小程序化 微前端框架

强势升级!融云上线第四代通信网 SD-CAN V4

融云 RongCloud

网络 通信 融云

软件测试/测试开发丨移动端App自动化之触屏操作自动化

测试人

软件测试 自动化测试 测试开发

MobTech|移动应用开发中的消息推送

MobTech袤博科技

类 ChatGPT 开源软件,开发者用的上吗?

开源雨林

人工智能 开源软件 ChatGPT

海泰方圆出席首届工业和信息化领域商用密码应用峰会

电子信息发烧客

牛客网内部最新出品—1658页《Java面试突击核心手册》几乎覆盖市面上所有面试考点

架构师之道

Java 程序员 面试

窗口管理器:Lasso 中文激活版

真大的脸盆

Mac Mac 软件 窗口管理 窗口管理工具

SpringCloud 整合Gateway服务网关

做梦都在改BUG

Java Spring Cloud Gateway 服务网关

大厂直通车!GitHub独一份的Jenkins+k8s核心知识笔记(全彩版)

做梦都在改BUG

Java Kubernetes k8s jenkins

火山引擎DataLeap一招教你避坑“数据开发”中的资源隔离问题

字节跳动数据平台

大数据 数据治理 资源隔离 数据研发 企业号 3 月 PK 榜

GitHub爆火!阿里高级架构师30天整理的《Java超全进阶教程》PDF版分享

开心学Java

Java redis JVM 架构师 进阶笔记

中间件:数字化时代系统集成商的得力助手

FinFish

中间件 系统集成 小程序容器 软件中间件

从数仓发展史浅析数仓未来技术趋势

华为云开发者联盟

数据库 后端 华为云 华为云开发者联盟 企业号 3 月 PK 榜

Rainbond的 Gateway API 插件制作实践

北京好雨科技有限公司

Kubernetes API Gateway rainbond

2023年Java岗面试八股文及答案整理(金三银四最新版)

采菊东篱下

Java 程序员 面试

MobTech|如何使用秒验

MobTech袤博科技

OneFlow源码解析:Eager模式下的设备管理与并发执行

OneFlow

C++开发者必读经典书籍推荐

小万哥

c++ 程序员 后端 开发 推荐书籍

车载小程序改善车载设备体验与性能,打造智能出行生态圈

没有用户名丶

小程序容器

小巧简单的图像处理软件:Acorn 激活版

真大的脸盆

Mac 图像处理 Mac 软件 图像编辑工具

在 Flutter 多人视频中实现虚拟背景、美颜与空间音效

声网

flutter AI 虚拟背景 美颜

中康数字科技:基于大模型的医学文本信息处理与抽取

飞桨PaddlePaddle

龙蜥白皮书精选:跨云-边-端的只读文件系统 EROFS

OpenAnolis小助手

镜像 操作系统 白皮书 龙蜥技术 EROFS

不会性能调优,被面试官狂虐!全靠阿里Java性能调优全彩手册死撑

做梦都在改BUG

Java 性能优化 JVM 性能调优

数禾科技 AI 模型服务 Serverless 容器化之旅

阿里巴巴云原生

阿里云 Serverless 云原生 Knative 容器化

女朋友不懂Spring事务原理,今天给她讲清楚了!

做梦都在改BUG

Java spring 事务

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