晴耕雨読

working in the fields on fine days and reading books on rainy days

[Hakell] パイプライン演算子の作り方

Haskellでパイプライン演算子 |> の実装方法について説明します。

パイプライン演算子

パイプライン演算子 |> とは F# や Elixir などにある演算子で、数学的にいうと、左辺の値を右辺の関数に適用します。 すなわち、

# パイプライン演算子
123 |> fun
# 普通の書き方
fun(123)

ということです。 また、関数をカリー化してもアリティが足りなければ関数なので、カリー化ができる言語であれば次のようにも書けます。

# パイプライン演算子
123 |> add(456)
# 普通の書き方
add(456)(123)  # カリー化がよくわからない人は add(456, 123) と読み替えても良いです

もちろん、パイプラインは複数に渡ってパイプすることもできます。

# パイプライン演算子
123 |> fun |> print
# 普通の書き方
print(fun(123))

Haskellでパイプライン演算子の実装

Haskellで中置演算子 |> の定義するには関数名の部分をパーレン () で囲むのと、引数を左辺と右辺の2つ用意する必要がありますが、それ以外は普通の関数定義と同じです。

infixl 1 |>
(|>) a f = f a

定義通り、左辺の値を右辺の関数に適用するだけです。 なお、演算子を定義する時にfixity宣言を「左結合」の「優先度1」という低い優先度をつけました。 左結合については左側から順番に評価するので特に説明はいらないと思いますが、なぜ優先度1にしたかというと、最低優先順位の優先度0にしてしまうと、右結合で優先度0の演算子「$」と混ざった時に評価できなくなるからです。

print $ 123 |> fun

# 実際の結果(エラー)
cannot mix ‘$’ [infixr 0] and ‘|>’ [infixl 0] in the same infix expression

# どちらも同じ優先度だと以下の2通りの評価結果があり得る
print(fun(123))
fun(print(123))

という訳で、右結合の演算子 $ と優先度が被らないように、パイプライン演算子の優先度は「1」が妥当だと思います。

確認用のプログラム

以下が演算子定義の確認に使ったプログラムです。 内容は 0〜2 を map で +1 した合計を求めます。

infixl 1 |>
(|>) a f = f a

main = do
  print $ [0,1,2] |> map (+1) |> foldl (+) 0