Edited at

Swiftで関数の合成

More than 3 years have passed since last update.

f(x)とg(x)という関数を合成して合成関数を作ることを式で表すと

(g ∘ f)(x) = g(f(x))

となります。関数gと関数fを合成したg ∘ fに引数xを渡した結果は、f(x)の結果を関数gに渡した結果と等しいということです。

(ここまで受け売り)

という記号は合成の記号です。

写像の合成 - Wikipedia

現在提供されているSwiftでは関数合成を行う演算子は提供されていません。しかしGitHubのSwiftリポジトリにはinternalでExperimentalとして関数合成を行う演算子が定義されています。

https://github.com/apple/swift/blob/master/stdlib/internal/SwiftExperimental/SwiftExperimental.swift#L44

infix operator  : CompositionPrecedence

precedencegroup CompositionPrecedence {
associativity: left
higherThan: TernaryPrecedence
}

public func <T, U, V>(g: @escaping (U) -> V, f: @escaping (T) -> U) -> ((T) -> V) {
return { g(f($0)) }
}

∘はU+2218 RING OPERATORだそうです。簡単に入力する方法がわかりませんが、試してみましょう。

func addOne(x: Int) -> Int {

return x + 1
}

func addTwo(x: Int) -> Int {
return x + 2
}

let addThree = addTwo addOne // (Int) -> Int

let result1 = addTwo(x: addOne(x: 0)) // 3
let result2 = addThree(0) // 3

1を加算する関数addOneと2を加算する関数addTwoをaddTwo ∘ addOneとして合成してaddThreeという関数を作り、addThreeに0を渡すと結果が3になりました。これはaddTwo(x: addOne(x: 0))の結果と等しいので関数合成ができているようです。

次に4を掛けるmultiplyFourという関数を定義してaddThreeと組み合わせて新しい関数を作ってみます。

func multiplyFour(x: Int) -> Int {

return x * 4
}

let op_add_multiply = multiplyFour addThree
let op_multiply_add = addThree multiplyFour

let result_add_multiply = op_add_multiply(0) // 12
let result_multiply_add = op_multiply_add(0) // 3

op_add_multiplyは(x + 3) * 4で、op_multiply_addは(x * 4) + 3となっているので結果が違っています。の右項から実行されているのが分かると思います。

さてここまでは引数と返値が同じ型でしたが違う型を使ってみましょう。

func toString(x: Int) -> String {

return String(x)
}

let addAndToString = toString addThree // (Int) -> String
let result_addAndToString = addAndToString(0) // "3"

(Int) -> IntであるaddThree関数と(Int) -> StringであるtoString関数を合成すると(Int) -> String型の関数ができました。この合成関数に0を与えると文字列"3"が得られます。3を加えて文字列に変換しているからです。fの返値とgの引数が同じ型であれば合成できるのです。addThree ∘ toStringと記述してしまうとtoStringの返値とaddThreeの引数の型が違っているのでエラーになります。

では二つ以上の引数の関数と一つの引数の関数を合成してみるとどうなるでしょう。

func add(x: Int, y: Int) -> Int {

return x + y
}

let addAndMultiplyFour = multiplyFour add // ((Int, Int)) -> Int
let result_addAndMultiplyFour = addAndMultiplyFour((1, 2)) // 12

(Int, Int) -> Int(Int) -> Intを合成してみると((Int, Int)) -> Intという関数になりました。引数がタプルになっています。

引数と返値がタプルの場合でも合成できるでしょうか。やってみましょう。

func addOneWithTuple(tuple: (Int, Int)) -> (Int, Int) {

return (tuple.0 + 1, tuple.1 + 1)
}

func multiplyTwoWithTuple(tuple: (Int, Int)) -> (Int, Int) {
return (tuple.0 * 2, tuple.1 * 2)
}

let addOneAndMultiplyTwoWithTuple = multiplyTwoWithTuple addOneWithTuple // ((Int, Int)) -> (Int, Int)
let result_addOneAndMultiplyTwoWithTuple = addOneAndMultiplyTwoWithTuple((1, 2)) // (.0 4, .1 6)

((Int, Int)) -> (Int, Int)の関数が出来上がりました。

このように関数の合成を行うと二つの関数から新しい関数を作成することができます。関数型プログラミングでは単純で小さい関数を作成し、それらを合成してさまざまな機能を作成していくようです。Swiftで関数型プログラミングを行うには関数合成があると便利です。

ただしはExperimentalとして実装されているに過ぎません。iOSアプリなどでは自分で定義して利用するか、もしくはSwiftz(もしくは簡易版のSwiftx)などのライブラリで提供されている関数合成演算子を利用するといいでしょう。ちなみにSwiftzではではなくという文字が関数合成演算子として用いられています。このはOption+8で入力することができ、より入力しやすいようになっています。