TL;DR

内容

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"

部分適用は、ワークフローの実装で依存性の注入を行うときに使われる。

キーワード