All Articles

既存コードを簡単にCurry化する方法

curry 化していない挨拶文作成関数

func greet(name: String, message: String) -> String {
    return "\(name)さん \(message)"
}

let hello = greet(name: "山田", message: "こんにちは")

引数を 2 つ引き渡しメッセージを作成

これを手動で Curry 化すると

func _greetCurry(name: String) -> (String) -> String {
    return { message in "\(name)さん \(message)"  }
}

↑ メッセージ文を引数として受け取る関数が戻り値の関数

let tanaka: (String) -> String = _greetCurry(name: "田中")
let hello2 = tanaka("こんにちは")
let hello3 = tanaka("こんばんは")

田中という姓が関数内にキャプチャー(部分適用)されたことにより、メッセージ部分だけ渡すことで挨拶文が作成できます。

今回はすごくシンプルな例であまり恩恵はないのですが、これが Curry 化のメリットです。

greet_greetCurry ソースコードが重複して宜しくない。

既存の関数から Curry 関数を用意する

そこで、greetから Curry 化するための関数を用意する。(今回の本題)

func curry<A, B, C>(_ f: @escaping(A, B) -> C) -> (A) -> (B) -> C {
    return { a in { b in f(a ,b) } }
}

戻り値の型は、ネストした関数 (A) -> (B) -> C です。

これを活用すると、以下のようになる。

let greetCurry = curry(greet(name:message:))

let hello4 = greetCurry("高橋")("こんばんわ")
let hello5 = greetCurry("高橋")("おはよう")

『おはよう』を固定して、名前を変えたメッセージを複数作りたい

curry 化した関数の第一引数を部分適用して、メッセージを作るので、第 1 引数と第 2 引数を入れ替える必要がある。 これには flip という関数を用意する

func flip<A, B, C>(_ f: @escaping((A) -> (B) -> C)) -> (B) -> (A) -> C {
    return { b in { a in f(a)(b) } }
}

高階関数を活用すると既存の関数から新しい形の関数を生み出すことができるので本当に強力ですね。

let flipGreet = flip(curry(greet(name:message:)))
let morningGreet = flipGreet("おはよう")

let hello6 = morningGreet("山田")
let hello7 = morningGreet("飯田")

curry 化する関数の引数が 3 つの場合の例

func greet(name: String, title: String, message: String) -> String {
    return "\(title) \(name)さん \(message)"
}

curry 化ジェネレータは、

func curry<A, B, C, D>(_ f: @escaping((A, B, C) -> D)) -> (A) -> (B) -> (C) -> D {
    return {a in { b in { c in f(a, b, c) } } }
}
let greetCurry2 = curry(greet(name:title:message:))
let hello8 = greetCurry2("田中")("Mr.")("お元気ですか")

感想

前の記事の感想と一緒になるが、高階関数の理解は深めていきたい。 これでよりスッキリコードが書けるようになりますね!