LYAHFGG:
今度は、
Functor
(ファンクター)という型クラスを見ていきたいと思います。Functor
は、全体を写せる (map over) ものの型クラスです。
本のとおり、実装がどうなってるかをみてみよう:
trait Functor[F[_]] { self =>
/** Lift `f` into `F` and apply to `F[A]`. */
def map[A, B](fa: F[A])(f: A => B): F[B]
...
}
これが可能とする演算子はこうなっている:
trait FunctorOps[F[_],A] extends Ops[F[A]] {
implicit def F: Functor[F]
////
import Leibniz.===
final def map[B](f: A => B): F[B] = F.map(self)(f)
...
}
つまり、これは関数 A => B
を受け取り F[B]
を返す map
メソッドを宣言する。コレクションの map
メソッドなら得意なものだ。
scala> List(1, 2, 3) map {_ + 1}
res15: List[Int] = List(2, 3, 4)
Scalaz は Tuple
などにも Functor
のインスタンスを定義している。
scala> (1, 2, 3) map {_ + 1}
res28: (Int, Int, Int) = (1,2,4)
この演算は Tuple の最後の値のみに適用されていることに注意。詳細は scalaz group での議論を参照。
Scalaz は Function1
に対する Functor
のインスタンスも定義する。
scala> ((x: Int) => x + 1) map {_ * 7}
res30: Int => Int = <function1>
scala> res30(3)
res31: Int = 28
これは興味深い。つまり、map
は関数を合成する方法を与えてくれるが、順番が f compose g
とは逆順だ。通りで Scalaz は map
のエイリアスとして ∘
を提供するわけだ。Function1
のもう1つのとらえ方は、定義域 (domain) から値域 (range) への無限の写像だと考えることができる。入出力に関しては飛ばして Functors, Applicative Functors and Monoids へ行こう (本だと、「ファンクターからアプリカティブファンクターへ」)。
ファンクターとしての関数 …
ならば、型
fmap :: (a -> b) -> (r -> a) -> (r -> b)
が意味するものとは?この型は、a
からb
への関数と、r
からa
への関数を引数に受け取り、r
からb
への関数を返す、と読めます。何か思い出しませんか?そう!関数合成です!
あ、すごい Haskell も僕がさっき言ったように関数合成をしているという結論になったみたいだ。ちょっと待てよ。
ghci> fmap (*3) (+100) 1
303
ghci> (*3) . (+100) $ 1
303
Haskell では fmap
は f compose g
を同じ順序で動作してるみたいだ。Scala でも同じ数字を使って確かめてみる:
scala> (((_: Int) * 3) map {_ + 100}) (1)
res40: Int = 103
何かがおかしい。fmap
の宣言と Scalaz の map
演算子を比べてみよう:
fmap :: (a -> b) -> f a -> f b
そしてこれが Scalaz:
final def map[B](f: A => B): F[B] = F.map(self)(f)
順番が完全に違っている。ここでの map
は F[A]
に注入されたメソッドのため、投射される側のデータ構造が最初に来て、次に関数が来る。List
で考えると分かりやすい:
ghci> fmap (*3) [1, 2, 3]
[3,6,9]
で
scala> List(1, 2, 3) map {3*}
res41: List[Int] = List(3, 6, 9)
ここでも順番が逆なことが分かる。
fmap
も、関数とファンクター値を取ってファンクター値を返す 2 引数関数と思えますが、そうじゃなくて、関数を取って「元の関数に似てるけどファンクター値を取ってファンクター値を返す関数」を返す関数だと思うこともできます。fmap
は、関数a -> b
を取って、関数f a -> f b
を返すのです。こういう操作を、関数の持ち上げ (lifting) といいます。
ghci> :t fmap (*2)
fmap (*2) :: (Num a, Functor f) => f a -> f a
ghci> :t fmap (replicate 3)
fmap (replicate 3) :: (Functor f) => f a -> f [a]
この関数の持ち上げ (lifting) は是非やってみたい。Functor
の型クラス内に色々便利な関数が定義されていて、その中の 1つに lift
がある:
scala> Functor[List].lift {(_: Int) * 2}
res45: List[Int] => List[Int] = <function1>
scala> res45(List(3))
res47: List[Int] = List(6)
Functor
は他にもデータ構造の中身を書きかえる >|
、as
、fpair
、strengthL
、strengthR
、そして void
などの演算子を可能とする:
scala> List(1, 2, 3) >| "x"
res47: List[String] = List(x, x, x)
scala> List(1, 2, 3) as "x"
res48: List[String] = List(x, x, x)
scala> List(1, 2, 3).fpair
res49: List[(Int, Int)] = List((1,1), (2,2), (3,3))
scala> List(1, 2, 3).strengthL("x")
res50: List[(String, Int)] = List((x,1), (x,2), (x,3))
scala> List(1, 2, 3).strengthR("x")
res51: List[(Int, String)] = List((1,x), (2,x), (3,x))
scala> List(1, 2, 3).void
res52: List[Unit] = List((), (), ())