Scalazは, 以下のような演算子をApplyで定義している: (m1 |@| m2 |@| m3) f
. このような, ちょっとかっこいい演算子はsyntax/というディレクトリ以下に実装されている.
これはマコーレー・カルキン演算子と呼ばれる. 実装が面白いので紹介する.
マコーレーカルキン演算子でN個の値をつなぐと, applyN
のことであると書いてある. 例えば, f1 |@| f2 |@| f3
は, 次に関数を受けて, apply3相当のことをする.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| /**
* DSL for constructing Applicative expressions.
*
* `(f1 |@| f2 |@| ... |@| fn)((v1, v2, ... vn) => ...)` is an alternative to `Apply[F].applyN(f1, f2, ..., fn)((v1, v2, ... vn) => ...)`
*
* `(f1 |@| f2 |@| ... |@| fn).tupled` is an alternative to `Apply[F].applyN(f1, f2, ..., fn)(TupleN.apply _)`
*
* Warning: each call to `|@|` leads to an allocation of wrapper object. For performance sensitive code, consider using
* [[scalaz.Apply]]`#applyN` directly.
*/
final def |@|[B](fb: F[B]) = new ApplicativeBuilder[F, A, B] {
val a: F[A] = self
val b: F[B] = fb
}
|
apply3は以下:
1
2
| def apply3[A, B, C, D](fa: => F[A], fb: => F[B], fc: => F[C])(f: (A, B, C) => D): F[D] =
apply2(tuple2(fa, fb), fc)((ab, c) => f(ab._1, ab._2, c))
|
ApplicativeBuilderというのが気になる. 3つの値を|@|
でつなぐ時, 以下のようなことが起こる.
f1 |@| f2
によってApplicativeBuilderが作られる(これはApplicativeBuilder2と言ってもよい). ApplicativeBuilderはapplyを持っていて, f: (A, B) => C
を受け取ると, これを適用する.- さらに
|@| f3
が続くと, ApplicativeBuilder3が作られて, これも同様.
ようするに, 今まで|@|
で結合した値の個数分のapplyNを発動させるような仕組みである. でも今までマコーレーが"結合した"女の数となると20では足りないね:-)
以下はapplyのコード:
1
2
3
4
5
| final class ApplicativeBuilder[M[_], A, B](a: M[A], b: M[B]) {
def apply[C](f: (A, B) => C)(implicit t: Functor[M], ap: Apply[M]): M[C] = ap(t.fmap(a, f.curried), b)
final class ApplicativeBuilder3[C](c: M[C]) {
def apply[D](f: (A, B, C) => D)(implicit t: Functor[M], ap: Apply[M]): M[D] = ap(ap(t.fmap(a, f.curried), b), c)
|
ap(t.fmap(a. f.curried), b)
というのは, Haskellでいうと, f <$> a <*> b
のことである. なので, apply2とかを使う方が綺麗でいいと思うのだが, なぜそうなっていないのかは不明. Scalaで関数型プログラミングをしようとすると, このcurriedというのがかなりばら撒かれるか, あるいはcurriedしなくても済むように, よくあるケースに対してライブラリが努力するという方向になる(Scalazはたぶんそうしている). 言語の方で自動的にcurried化するようにがんばって欲しいのだが, 無理なんだろうか?
(追記)
ApplicativeBuilderは, 古いソースを参照していたようです. 新しいソースではちゃんと直ってる. (xuwei_kさんが教えてくれた)
1
2
3
4
5
| private[scalaz] trait ApplicativeBuilder[M[_], A, B] {
val a: M[A]
val b: M[B]
def apply[C](f: (A, B) => C)(implicit ap: Apply[M]): M[C] = ap.apply2(a, b)(f)
|