はじめに
カリー化や部分適用は、もともとは関数型言語から輸入された概念ですが、Swiftを含む様々な言語で使うことができるテクニックです。
少し難しいので、まずは具体的な例を使って説明しましょう。
具体例
既存の関数として、次のような関数があるとします。
/// `text`を`count`回反復した文字列を返す関数。
func repeatText(_ text: String, count: Int) -> String {
String(repeating: text, count: count)
}
使ってみるとこんな感じ。
repeatText("Hey", count: 3) // -> "HeyHeyHey"
repeatText("Hey", count: 5) // -> "HeyHeyHeyHeyHey"
repeatText("Hey", count: 2) // -> "HeyHey"
しかし使ってるうちに、「最初の引数の値を固定して使い回せないかな」と思うようになりました。
let repeatHeyFor = { count in repeatText("Hey", count: count) }
repeatHeyFor(3) // -> "HeyHeyHey"
repeatHeyFor(5) // -> "HeyHeyHeyHeyHey"
repeatHeyFor(2) // -> "HeyHey"
これで、引数text
の値を固定することで、2つあった引数を1つに減らすことができましたね。
これを部分適用と言います。
さらには、text
に色んな値が固定できるように、こんなのを定義してみました。
let repeatTextWithText = { text in
{ count in repeatText(text, count: count) }
}
let repeatHeyFor = repeatTextWithText("Hey")
let repeatYeahFor = repeatTextWithText("Yeah")
repeatHeyFor(3) // -> "HeyHeyHey"
repeatYeahFor(2) // -> "YeahYeah"
少し複雑そうに見えますが、クロージャを2重にしているだけです。
それでは、元の関数repeatText
と、repeatTextWithText
を比較してみましょう。
before: repeatText("Hey", count: 3)
after: repeatTextWithText("Hey")(3)
いっぺんに渡す必要があった2つの引数を、1つと1つに分割して渡せるようになっていますね。
この変換のことをカリー化と言います。
ちなみに元の関数にはcount:
のように引数ラベルが付いていますが、カリー化するとクロージャなので消えます。代わりに変数名で引数の情報を補うと良いでしょう。(この例の-WithText
や-For
のように)
カリー化と部分適用の違い
より一般化して書くとこんな感じです。
// 元の関数 引数 戻り値
let f: (A, B) -> C
// 部分適用の操作
let partial: ((A, B) -> C, A) -> (B) -> C
// カリー化の操作
let curry: ((A, B) -> C) -> (A) -> (B) -> C
部分適用は、一部の引数の値を固定して、引数の数を減らした関数を作ること。
カリー化は、複数の引数を持つ関数を、分割して渡せるような関数にすること。
f
をカリー化して A
を渡す操作は、部分適用になります。
どういう時に便利か
関数を利用する際に、他の引数の値は変わるけど、特定の引数の値は一度決まったら基本的にコンスタントだなぁという時には、部分適用やカリー化を検討してみると良いかもしれません。
参考