5
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

Haskellの($)と(.)の違い

Haskellの$.

$.hs
f $ g x =  f (g x)
.hs
(f . g) x = f (g x)

どちらも同じに見えるんですけど。
Haskellを学びはじめてから、結構長い間これが分からなかったのでまとめ。

結論

func x = f (g (h x))$,.を使うとOKのようには書けるがNGのようには書けない。

$ .
func x = f $ g $ h $ x OK func x = f . g . h $ x OK
func x = f $ g $ h x OK func x = f . g . h x NG
func = f $ g $ h NG func = f . g . h OK

Haskllerは綺麗なモノが大好きなので誰に教わることもなく、慣れるに従って、括弧よりも$$よりも.を使うようになる気がする。

具体例

たとえば、

  • 1から10までの数字のうち
  • 奇数のものに
  • すべてに2を足したうえで
  • 合計して
  • 印刷する
basic.hs
main = do
  let list = [1..10]
  let odds = filter odd list
  let plus = map (+2) odds
  let result = sum plus
  print result

括弧

括弧で書くと

paren.hs
print (sum (map (+2) (filter odd [1..10])))

($)

これを$で書き換える。
左のカッコを$で置き換え対応する右カッコを削る、またはひとつひとつの処理を$というパイプでつなぐイメージ。

$.hs
print $ sum $ map (+2) $ filter odd [1..10]
-- または
print $ sum $ map (+2) $ filter odd $ [1..10]

あえて二つ目を示した理由は次でわかる。

(.)

今度は関数合成.を使う。$版から書き換えるなら最後の$以外は.に置き換えることができる。

.hs
print . sum . map (+2) . filter odd $ [1..10]

また、最後の引数を落とすと、この式は型(Integral a, Show a) => [a] -> IO ()の関数となるので

ok.hs

> f = print . sum . map (+2) . filter odd
> f [1..10]
35

もOK。
今回のケースではいわゆるポイントフリースタイルがもっともシンプルなので具体例の回答としてはこれがベストと思う。

($)のNGケース

ひとつ前の例で.$に単純に置き換えると、

ng.hs
> f = print $ sum $ map (+2) $ filter odd

<interactive>:6:30: error:
    ? Couldn't match expected type [Integer]
                  with actual type [Integer] -> [Integer]
    ? Probable cause: filter is applied to too few arguments
    ...............................

これはNG。
map (+2)Integerのリストを期待しているのにfilter oddは部分適用された関数[Integer]->[Integer]なので型が合わない。

(.)のNGケース

逆に$でOKのケースを.に単純に置き換えた次のケースは

ng2.hs
> print . sum . map (+2) . filter odd [1..10]

<interactive>:7:26: error:
    ? Couldn't match expected type a -> [Integer]
                  with actual type [Integer]
    ? Possible cause: filter is applied to too many arguments
   ........................

やはりNG。これは

ng2'.hs
> print . sum .  map (+2) . (filter odd [1..10])

こう書いたのと同じことになる。.は関数を合成しようとして引数には前後とも関数を期待しているのに(filterは自身の引数と先に結合してしまうため)第二引数がリストになってしまう

期待されるのは括弧で書くと

ok2.hs
> (print . sum .  map (+2) . filter odd) [1..10]
35

なので、$を使って括弧を除くと

.hs
print . sum . map (+2) . filter odd $ [1..10]

こうなる。上で最後の$以外はとしたのは、このような事情から。
合成関数に直接値を喰わせるときは.でも空白区切りでもなく$.これは大事。

補足

$.の型と実装を比較。

$dot.hs
($) :: (a -> b) -> a -> b
f $ x =  f x

(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)

$は一つだけでは見かけ上、何もしない。
しかし、f xと書いた場合fxは左結合で、どんな中置演算子より強く結びつくが、$を使うことで右結合かつ優先順位最低(ゼロ)となることで、複数指定した場合に括弧の入れ子を単なる連結に変えてみせることができる。

一方、.は関数を二つとって関数を返す。
まさに関数を合成しているわけだが、実装は第二引数の関数へ値を適用した結果を第一引数の関数に食わせるもの。
(関数合成というと何やら黒魔術めいたもので複数の関数の機能を一つに錬成するようなイメージを持っていました(笑)。そのため難しいものだと思って初めは敬遠していたのですが、実際は何のことは無いパイプラインです。小さく作ってじゃんじゃん繋ぎましょう)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
5
Help us understand the problem. What are the problem?