.
と <$>
の順序のメモ
Haskellにおいてしょっちゅうお世話になる演算子二人組を連れてきたよ!
infixr 9 .
(.) :: (b -> c) -> (a -> b) -> a -> c
infixl 4 <$>
(<$>) :: Functor f => (a -> b) -> f a -> f b
...でもたまに分からなくなるよ。
考えたいケース
以下はまだわかる
(+ 1) <$> [1..5]
でも以下のように書くと、 "Why NOT?" とか言って書き換えを提案されるよ!
(* 2) <$> (+ 1) <$> [1..5]
-- why not
(* 2) . (+ 1) <$> [1..5]
Found: print <$> (++ " ") <$> getLine Why not: print . (++ " ") <$> getLine
で、素直にこれで書き換えられるのも分かるのだけど きちんと論理的に考えてみるのです。
論理的に考えるあるよ
(* 2) . (+ 1) <$> [1..5]
解釈を考えるあるよ。基本的に左から読まれるわけだけど(ほんとか?)、
今回は中置演算子で繋がれているので infix
の値を確認するよ。
infix
は、 値が大きければ大きい程優先される 。
冒頭でも書いたけど、 srchaskell{(.)} は infix 9
で
srchaskell{(<$>)} は infix 4
。
なので、先に .
での束縛が行われるよ。つまり:
((* 2) . (+ 1)) <$> [1..5]
こうだね。 今度はそれぞれの型を考えてみるよ。
(* 2) :: Num a => a -> a
(+ 1) :: Num a => a -> a
(.) :: (b -> c) -> (a -> b) -> a -> c
(* 2) . :: Num a' => (a -> a') -> a -> a'
(* 2) . (+ 1) :: Num a' => a' -> a'
これを <$>
と一緒にしてみると
(* 2) . (+ 1) :: Num a' => a' -> a'
(<$>) :: Functor f => (a -> b) -> f a -> f b
(* 2) . (+ 1) <$> :: (Num a', Functor f) => f a' -> f a'
なるほど。ここで晴れて [1..5]
を適用できる。
(* 2) . (+ 1) <$> [1..5] :: Num a' => [a']
わーい!
まとめ(?)
型パズル、単純に楽しかった!