web-dev-qa-db-ja.com

Haskellの関数合成が正しい結合性であるのはなぜですか?

数学的には、関数合成演算は結合法則です。したがって:

f . (g . h) = (f . g) . h

したがって、関数合成操作は、左結合性または右結合性のいずれかとして定義できます。

Haskellでの通常の関数適用(つまり、$演算ではなく、用語の並置)は結合性のままであるため、私の意見では関数構成も結合性のままにする必要があります。結局のところ、世界中のほとんどの人(私を含む)は左から右に読むことに慣れています。

それにもかかわらず、Haskellの関数合成は正しい結合性です:

infixr 9 .

関数合成操作が左結合であるか右結合であるかは、実際には違いがないことを私は知っています。それにもかかわらず、なぜそれが結合性のままにされないのか知りたいです。この設計上の決定には、次の2つの理由が思い浮かびます。

  1. Haskellのメーカーは、関数の合成が$操作と論理的に同じであることを望んでいました。
  2. Haskellのメーカーの1つは、関数合成を左結合ではなく右結合にする方が直感的であると感じた日本人でした。

冗談はさておき、Haskellで関数合成が正しく結合する有益な理由はありますか? Haskellの関数合成が結合性のままになっているとしたら、何か違いはありますか?

33
Aadit M Shah

厳密でない評価が存在する場合、右結合性は有用です。非常にばかげた例を見てみましょう:

foo :: Int -> Int
foo = const 5 . (+3) . (`div` 10)

わかりました。.infixrのときに、この関数が0で評価されるとどうなりますか?

foo 0
=> (const 5 . ((+3) . (`div` 10))) 0
=> (\x -> const 5 (((+3) . (`div` 10)) x)) 0
=> const 5 (((+3) . (`div` 10)) 0)
=> 5

では、.infixlだったらどうなるでしょうか?

foo 0
=> ((const 5 . (+3)) . (`div` 10)) 0
=> (\x -> (const 5 . (+3)) (x `div` 10)) 0
=> (const 5 . (+3)) (0 `div` 10)
=> (\x -> const 5 (x + 3)) (0 `div` 10)
=> const 5 ((0 `div` 10) + 3)
=> 5

(私はちょっと疲れています。これらの削減手順で間違いを犯した場合は、お願い私に知らせてください。または単に修正してください。)

はい、同じ結果になります。しかし、削減ステップの数は同じではありません。 .が左結合である場合、特に、チェーンの初期の関数がネストされた計算の結果を必要としないようにショートカットを決定した場合、合成操作をさらに減らす必要がある場合があります。最悪の場合も同じですが、最良の場合は、右結合性が有利になる可能性があります。ですから、時々悪い選択ではなく、時々良い選択を選んでください。

36
Carl