2つの関数 f : A -> B
, g : B -> C
と値 x : A
があるとき、そのまま関数適用すると
g (f x)
となるところを、Elmではいくつかの方法で書き換えることができます。
g <| f <| x
g << f <| x
x |> f |> g
x |> f >> g
演算子としての適用順に注目しながら確認してみましょう。
パイプライン演算子(Pipeline Operator)
演算子という呼び名がありますが、実際は次のようなシグネチャを持つ関数です。
(<|) : (a -> b) -> a -> b
(|>) : a -> (a -> b) -> b
そのため、次のような関数適用も可能です。
(<|) g (f x)
リテラルで中置演算子として適用できるので、結果として以下のように書けます。
g <| f x
同じく f x
も f <| x
と表現できるので、適用の順番を考慮すると g <| (f <| x)
と書くのが妥当ですが、これを g <| f <| x
と表記することもできます。これは <|
演算子の結合順が右結合に指定されているからです。
これがもし左結合だとシグネチャが合わないので動作しません。
全く推奨できませんが、以下のように2引数関数に <|
を使って適用させることも可能ではあります。
しかし、括弧の使用から分かるように無理やり左結合として解釈させているため不自然です。
z = \x -> (\y -> x + y)
(z <| 2) <| 3
同様に右向きのパイプライン演算子でも表記できます。
x |> f |> g
関数の適用順が左から右になって読みやすいですね。こちらの結合順は (x |> f) |> g
と解釈されるので左結合です。
一方で、異なる向きのパイプライン演算子は括弧なしでは混同できません。
仮に f = \x -> x + 1
, g : \x -> x * 2
でシグネチャが同じだったとしても、
(g <| x) |> f
g <| (x |> f)
は結果が異なるので定義が曖昧です。エラーメッセージも次のような文言が出ます。
Conflicting associativity for binary operators (<|, |>). Consider adding
parentheses to disambiguate.
たとえ括弧を使ったとしても大変読みにくいのでこのような混同は避けるべきでしょう。
ちなみに、 <|
はHaskellで言うところの ($) :: (a -> b) -> a -> b
と同じです。
関数合成(Composition)
パイプライン演算子と似たような記号ですが、2つの関数を合成する関数があります。
(<<) : (b -> c) -> (a -> b) -> a -> c
(>>) : (a -> b) -> (b -> c) -> a -> c
これも中置演算子で、上記の例だと g << f
や f >> g
のように使われています。
これを用いて g (f x)
を表現しようとすると
(g << f) x
のようになります。これに先程のパイプライン演算子を組み合わせると
(g << f) <| x
となります。ここからさらに括弧を外して
g << f <| x
と書くこともできます。これは先程の(同じ演算子同士の)結合順ではなくて、演算子の優先順です。すなわち <<
が <|
よりも優先して処理されていることがわかります。
同じ向きの関数合成同士ではどうでしょうか。考えてみるとわかりますが、 h << g << f
を (h << g) << f
と解釈しても h << (g << f)
と解釈しても結果は同じはずです。
そのため左右どちらの結合としても解釈できます。個人的にはパイプライン演算子と同様に <<
を右結合とみなしても良いように感じます。
逆向きの f >> g >> h
, x |> f >> g
も同様です。
パイプライン演算子のときと同様に、異なる向きの関数合成を h << g >> f
のように混ぜようとするとエラーが発生します。
Conflicting associativity for binary operators (<<, >>). Consider adding
parentheses to disambiguate.
ちなみに、 <<
はHaskellにおける (.) :: (b -> c) -> (a -> b) -> a -> c
と同じです。
まとめ
- 結合順を意識する
-
<<, >>
が<|, |>
より優先される - 矢印の順に折りたたまれるように結合されると考えると覚えやすい?
-
- 異なる向きの演算子を混ぜるべきではない
- ただし、あんまり複雑に組み合わせることを考えるのではなく、意味のある単位で関数に置き換える(シンプルだけど重要)
蛇足:紹介しなかった組み合わせ
g << f <| x
ではなく、逆の場合はどうなるでしょうか?以下の例で考えてみましょう。
f = \x -> x + 1
g = \x -> x * 2
h = \fun -> fun 3
h <| g << f