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で入力することができ、より入力しやすいようになっています。