管道

ReScript 提供了一个小巧但好用的操作符 ->,称为“管道”,它允许你由内向外“翻转”代码。a(b) 可以写成 b->a。这是一个简单的语法,没有任何运行时开销。

为什么你要使用它?想象下面的情况:

ReScriptJS Output
validateAge(getAge(parseData(person)))

这有点难读,因为你需要从最里面的部分开始,读到最外面的部分。使用管道来变为链式调用:

ReScriptJS Output
person
  ->parseData
  ->getAge
  ->validateAge

parseData(person) 被转换为 person->parseDatagetAge(person->parseData) 被转换为 person->parseData->getAge,依此类推。

当函数需要一个以上的参数时这也是可行的

ReScriptJS Output
a(one, two, three)

这和下面是一样的:

ReScriptJS Output
one->a(two, three)

管道也适用于带标签的参数。

管道可以用来模拟面向对象编程。例如,在 Java 等其他语言中是 myStudent.getName,在 ReScript 中可以写成 myStudent->getName(相当于 getName(myStudent))。这使我们能够拥有 OOP 的可读性,又不必引入一个巨大的类系统,而引入这个巨大系统的唯一目的还只是为了用函数调用对象的一块数据。

技巧和诀窍

不要滥用管道;它们是达到目的的一种手段。没有经验的工程师有时会把库 API 专门设计成可以使用管道的样子,这就本末倒置了。

JS 方法链

阅读这一小节需要先理解 JS 绑定 API

JavaScript 的 API 通常关联在对象上,而且通常是可以链式调用的,像这样:

JS
const result = [1, 2, 3].map(a => a + 1).filter(a => a % 2 === 0); asyncRequest() .setWaitDuration(4000) .send();

假设我们不需要上面的链式调用,我们可以使用 @send 来分别绑定链式调用中的各个函数,@send 的文档在上面给出的链接中。

ReScriptJS Output
type request
@val external asyncRequest: unit => request = "asyncRequest"
@send external setWaitDuration: (request, int) => request = "setWaitDuration"
@send external send: request => unit = "send"

你可以这样使用它们:

ReScriptJS Output
let result = Js.Array2.filter(
  Js.Array2.map([1, 2, 3], a => a + 1),
  a => mod(a, 2) == 0
)

send(setWaitDuration(asyncRequest(), 4000))

这看起来比对应的 JS 部分要糟糕得多!用管道整理它们:

ReScriptJS Output
let result = [1, 2, 3]
  ->Js.Array2.map(a => a + 1)
  ->Js.Array2.filter(a => mod(a, 2) == 0)

asyncRequest()->setWaitDuration(4000)->send

在管道中使用变体

你可以在管道中像函数一样使用变体构造器:

ReScriptJS Output
let result = name->preprocess->Some

我们会把它变成:

ReScriptJS Output
let result = Some(preprocess(name))

注意,只有在管道中才能把变体构造器当成函数使用,其他任何地方都不行。

管道占位符

通过下划线来表达一个占位符,它告诉 ReScript 你想在之后填入函数的一个参数。下面的两行代码是一个意思:

RES
let addTo7 = (x) => add3(3, x, 4) let addTo7 = add3(3, _, 4)

有时你不想把你拥有的值通过管道输送到第一个位置。在这些情况下,你可以标记一个占位符,来表示你想通过管道传到哪个位置。

假设你有一个 namePerson 函数,它接受一个 person 和一个 name 参数。如果你想用管道传递一个 person,可以这样做:

ReScriptJS Output
makePerson(~age=47, ())
  ->namePerson("Jane")

如果你有一个 name,并且想通过管道操作符作用到一个 person 对象上,你可以使用一个占位符:

ReScriptJS Output
getName(input)
  ->namePerson(personDetails, _)

这允许你将值通过管道传到任意参数位置。它也适用于命名参数:

ReScriptJS Output
getName(input)
  ->namePerson(~person=personDetails, ~name=_)

三角管道操作符(已废弃)

你可能在一些代码库中见过另一种管道,|>。它们已经被废弃了。

-> 管道不同,|> 管道将参数传入最后一个参数(而不是第一个)。a |> f(b) 变为 f(b, a)

关于这两个运算符的原理和区别的更深入讨论,请参考 Data-first and Data-last comparison by Javier Chávarri