TL;DR
- 関数型プログラミングでは関数を「もの」として扱い、他の関数の入力や出力に使える。
- F# の関数はすべてカリー化されており、部分適用がワークフローでの依存性の注入に使われる。
内容
Understanding Functions の一節である。
関数型プログラミングでは、関数は「もの」として扱われる。そのため、関数を他の関数の入力として渡したり、出力として返したりできる。関数をパラメータとして渡せば、その関数の動作を制御できる。このような関数を高階関数と呼ぶ。
F# で関数を「もの」として扱う
通常の関数定義と、無名関数(ラムダ式)は次のように書く。
let plus3 x = x + 3
let times2 x = x * 2
let square = (fun x -> x * x)関数は変数に束縛したり、リストにまとめたりできる。リストをループして、各関数を順に評価することもできる。
let addThree = plus3
let listOfFunctions = [addThree; times2; square]
for fn in listOfFunctions do
let result = fn 100
printfn "If 100 is the input, the output is %i" result入力としての関数
関数を入力として受け取る関数を考える。次の関数は、int -> int の任意の関数を受け取れる。
// val evalWith5WithAdd2: fn: (int -> int) -> int
let evalWith5WithAdd2 fn =
fn(5) + 2
let add1 x = x + 1
evalWith5WithAdd2 add1 // val it: int = 8
let square x = x * x
evalWith5WithAdd2 square // val it: int = 27出力としての関数
関数を返すと、関数に特定のパラメータを組み込める。次の加算器ジェネレータは、int を受け取って int -> int の関数を返す。無名関数の代わりに名前付きの関数を返してもよい。
// val adderGenerator: numberToAdd: int -> x: int -> int
let adderGenerator numberToAdd =
fun x -> numberToAdd + x
let add1 = adderGenerator 1 // val add1: (int -> int)
add1 2 // val it: int = 3
let add100 = adderGenerator 100 // val add100: (int -> int)
add100 2 // val it: int = 102カリー化
関数を返せるなら、複数のパラメータを取る関数を、1 つのパラメータを取る関数の連なりに変換できる。これをカリー化と呼ぶ。F# では、すべての関数がカリー化されている。
部分適用
関数がカリー化されていれば、複数のパラメータを取る関数に引数を 1 つだけ渡すと、残りのパラメータを受け取る新しい関数が返る。これを部分適用と呼ぶ。
let sayGreeting greeting name =
printfn "%s %s" greeting name
let sayHello = sayGreeting "Hello"
let sayGoodbye = sayGreeting "Goodye"
sayHello "Alex"
sayGoodbye "Alex"部分適用は、ワークフローの実装で依存性の注入を行うときに使われる。