lift

(译者注:此处原标题是“Bro, do you even lift?”,是一流行语,发源于健身圈,指质疑别人的健身方式和效果并显示优越感,后扩散至其他领域。再注:作者书中用了不少此类俚语或俗语,有时并非在使用俚语的本意,就像这句,完全就是为了好玩。另,关于 lift 的概念可参看第 8 章。)

我们来试试以一种 pointfree 的方式调用 applicative functor。因为 map 等价于 of/ap,那么我们就可以定义无数个能够 ap 通用函数。

  1. var liftA2 = curry(function(f, functor1, functor2) {
  2. return functor1.map(f).ap(functor2);
  3. });
  4. var liftA3 = curry(function(f, functor1, functor2, functor3) {
  5. return functor1.map(f).ap(functor2).ap(functor3);
  6. });
  7. //liftA4, etc

liftA2 是个奇怪的名字,听起来像是破败工厂里挑剔的货运电梯,或者伪豪华汽车公司的个性车牌。不过你要是真正理解了,那么它的含义也就不证自明了:让那些小代码块发生 lift,成为 applicative functor 中的一员。

刚开始我也觉得这种 2-3-4 的写法没什么意义,看起来又丑又没有必要,毕竟我们可以在 JavaScript 中检查函数的参数数量然后再动态地构造这样的函数。不过,局部调用(partially apply)liftA(N) 本身,有时也能发挥它的用处,这样的话,参数数量就固定了。

来看看实际用例:

  1. // checkEmail :: User -> Either String Email
  2. // checkName :: User -> Either String String
  3. // createUser :: Email -> String -> IO User
  4. var createUser = curry(function(email, name) { /* creating... */ });
  5. Either.of(createUser).ap(checkEmail(user)).ap(checkName(user));
  6. // Left("invalid email")
  7. liftA2(createUser, checkEmail(user), checkName(user));
  8. // Left("invalid email")

createUser 接收两个参数,因此我们使用的是 liftA2。上述两个语句是等价的,但是使用了 liftA2 的版本没有提到 Either,这就使得它更加通用灵活,因为不必与特定的数据类型耦合在一起。

我们试试以这种方式重写前一个例子:

  1. liftA2(add, Maybe.of(2), Maybe.of(3));
  2. // Maybe(5)
  3. liftA2(renderPage, Http.get('/destinations'), Http.get('/events'))
  4. // Task("<div>some page with dest and events</div>")
  5. liftA3(signIn, getVal('#email'), getVal('#password'), IO.of(false));
  6. // IO({id: 3, email: "gg@allin.com"})

操作符

在 haskell、scala、PureScript 以及 swift 等语言中,开发者可以创建自定义的中缀操作符(infix operators),所以你能看到到这样的语法:

  1. -- haskell
  2. add <$> Right 2 <*> Right 3
  1. // JavaScript
  2. map(add, Right(2)).ap(Right(3))

<$> 就是 map(亦即 fmap),<*> 不过就是 ap。这样的语法使得开发者可以以一种更自然的风格来书写函数式应用,而且也能减少一些括号。