かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

手軽なスクリプト言語としてのF# その14「合成演算子とパイプ演算子」

さて、ついに関数型言語っぽい機能の説明です!!!合成演算子とパイプ演算子について紹介したいと思います。

合成演算子

合成演算子というのは、その名の通り2つの関数をくっつけてしまう演算子です。前方合成演算子>>と後方合成演算子<<の2種類があります。
例えばf1とf2という関数があってf1 >> f2のようにするとf1を呼び出した結果をf2に渡して、その結果を返すという1つの関数になります。f1 << f2はf2を呼び出した結果をf1に渡して、その結果を返すという関数になります。実際に書いて動きを見てみましょう。

// 元になる関数
let f1 x = 
    printfn "call f1"
    x + 1
let f2 x = 
    printfn "call f2"
    x + 2
let f3 x =
    printfn "call f3"
    x + 3

// 前方と後方でそれぞれf1 f2 f3の順番で合成
let f4 = f1 >> f2 >> f3
let f5 = f1 << f2 << f3

// 合成した結果を呼び出してみる
printfn "f4 1 = %d" (f4 1)
printfn "----"
printfn "f5 1 = %d" (f5 1)

printfn "----"
// こういう風に合成した結果に対して関数呼び出しを直接やることも出来る
let r1 = (f1 >> f2 >> f3) 1
let r2 = (f1 << f2 << f3) 1
printfn "r1 = %d, r2 = %d" r1 r2

実行結果

call f1
call f2
call f3
f4 1 = 7
----
call f3
call f2
call f1
f5 1 = 7
----
call f1
call f2
call f3
call f3
call f2
call f1
r1 = 7, r2 = 7

前方合成演算子は合成した順番に、後方合成演算子は合成した順番とは逆の順番で呼ばれてるのがわかると思います。

パイプ演算子

次はパイプ演算子です。これも前方パイプ演算子|>と、後方パイプ演算子<|があります。これは関数を合成するのではなくて、値を関数に適用していくという操作を連続してやるようなイメージです。

例えば以下のようなコードがあったとします。

10 |> f1 |> f2

これは10という値を引数にf1を呼び出して、その結果を引数にしてf2を呼び出すという形になります。

後方パイプ演算子はこれが逆向きになります。以下の例だと10を引数にしてf1を呼び出します。

f1 <| 10

実際にプログラムを書いて動きを見てみます。

// 元になる関数
let f1 x = 
    printfn "call f1"
    x + 1
let f2 x = 
    printfn "call f2"
    x + 2
let f3 x =
    printfn "call f3"
    x + 3

// 前方
let r1 = 10 |> f1 |> f2 |> f3
printfn "------"
// 後方
let r2 = f1 <| (f2 <| (f3 <| 10))

printfn "r1 = %d, r2 = %d" r1 r2

実行結果は以下のようになります。

call f1
call f2
call f3
------
call f3
call f2
call f1
r1 = 16, r2 = 16

後方パイプ演算子は、優先順位の関係で括弧を付けて呼び出し順番を明示的に指定しています。さて、この例を見ると、前方パイプ演算子は、関数を連続的に呼び出せてステキ!!と感じますが、後方は何か括弧とかあってイマイチ・・・と感じてしまいます。

そんなイマイチな、後方パイプ演算子の使いどころとしては、関数に関数の結果を引数にして渡したいという状況下においてコードを簡潔に書くという用途として使うのが一般的?っぽいです。実際に例を示してみると以下のようなコード例です。後方パイプ演算子を使った方がすっきりとかけてることがわかると思います。

// 渡された関数に10を適用した結果を表示する
let f x = 
    let v = x 10
    printfn "x(10) = %d" v

// ラムダ式を普通に引数に渡す場合
f(fun x -> x * x)

// 前方パイプ演算子の場合
(fun x -> x * x) |> f

// 後方パイプ演算子を使って渡す場合
f <| fun x -> x * x

実行結果は割愛しますが、後方パイプ演算子を使った場合括弧をつけなくてもいいんです!!!どうです?凄いでしょ?
個人的には、あまり有難味は感じませんが!!