Haskellの勉強をしていたら、Elixirのパイプライン演算子がF#由来であることを知った

Elixirというプログラミング言語に、パイプラインマクロ演算子というのがあり、面白いなーと思っていた。お前らElixirとか知らないだろうから、これがどんなものか後で書くとして、Haskellの勉強をしていたら同等のものを実装している記事をみつけ、どうもこれはF#由来であることを知った。


さて、くだらない例だけど、数のリストに何か(map)して条件を満たすものを抜き出して(filter)、最後に足す(fold)みたいなことって、Haskellでは普通たとえば次のように書く(と思う)

Prelude>  sum . filter even . map (+1) $ [1..10]
30

Haskellなんて知らない人もいると思うので、ほぼ同等のコードをPerlで書くとこうなる

use strict;
use warnings;
use List::Util qw/sum/;

my $ans = sum grep { $_ % 2 == 0 } map { $_ + 1 } (1 .. 10);
print( $ans );

細かな文法の違いはあれ、やってることは同じだ。
Rubyの人ならこう書きたいかもしれない

irb> (1..10).map{|x| x + 1 }.select{|n| n.even? }.reduce(0){|a, b| a + b }
=> 30

どちらがいいということはないのだけど、行う処理の内容によってはRubyのような順番で書いたほうがわかりやすいこともある。(Unixのコマンドをパイプラインでつないでいくことを思い出してほしい)
F#にはパイプライン演算子 |> という、そういうことを実現する演算子があるらしい。Haskellではそれを実現する演算子(というかただの関数)を簡単に書くことができ、次のように書く

-- |pipeline operator
-- >>> sum . filter even . map (+1) $ [1..10]
-- 30
--
-- >>> [1 .. 10] |> map (+1) |> filter even |> sum
-- 30
infixl 0 |>
(|>) :: a -> (a -> b) -> b
x |> f = f x 

Elixirのパイプラインは昔はマクロで実装されていて名前も /> だったが、最近のリリース(0.7.2)で組み込みの演算子になって名前も |> になったようだ。

iex(4)> [1,2,3,4] |> Enum.map(&1 + 2) |> Enum.filter(fn(x) -> rem(x, 2) == 0 end) |> List.foldl(0, &1 + &2)
10

なおElixirの関数はHaskellみたいにカリー化されていたりはしないので、普通はこんなもの作れない。結局組み込み演算子になったが、もしこういうのをユーザが作ろうとしたらマクロ(Exlirのマクロは抽象構文木を操作する本物のマクロだ)でも使うしかないと思う。ということは本物のマクロがない言語だと後からこういうのを作るのは難しい。


さて、これだけだと id:kazu-yamamoto さんの記事を読めばいいっていう話でつまらないので、F#の関数合成演算子 >> と同等のものをHaskellで作ってみた。いや、作ってみたといってもこれも糞簡単なのだが。
Haskellにはもちろん関数合成演算子 . があるし上でも使っているが、F#のそれとは逆順である。 |> で値を渡していくのは楽しいが、引数を最初に渡さなければいけないので、結果として関数が欲しい時はラムダ式にするしかない。

Prelude> (\s -> s |> map (+1) |> filter even |> sum) [1 .. 10]
30

でもたぶんHaskellだと、そのような関数を作り出したいこともあるだろう。逆順の関数合成演算子 |>> は以下。

-- |pipelined compose operator
-- >>> (map (+1) |>> filter even |>> sum |>> show |>> putStr) [1 .. 10]
-- 30
--
-- >>> :m + Data.List
-- >>> ( words |>> sort |>> group |>> map (\ls -> (head ls, length ls)) ) "ab a b a a b"
-- [("a",3),("ab",1),("b",2)]
infixl 0 |>>
(|>>) :: (a -> b) -> (b -> c) -> a -> c
g |>> f = f . g


あ、ソースコードはgithubにあげた。doctest形式の使用例兼単体テストもある。