Swiftでアプリカティブスタイル

More than 3 years have passed since last update.

Swift を書いていると Optional を多用しますが、次のように Optional に包まれた値同士を計算したいときに面倒です。


Swift

let a: Int? = 2

let b: Int? = 3

// a + b をしたい(ただし、 a か b が nil なら nil を得たい)


ぱっと思い付くのは次のような方法です。

方法1: 面倒すぎる


Swift

let r1: Int? = {

if let a0 = a {
if let b0 = b {
return a0 + b0
}
}
return nil
}()

方法2: nil が入ってたら即死


Swift

let r2: Int? = a! + b!


方法3: 結果が Int?? になってしまう(二重に Optional に包まれる)


Swift

let r3 = a.map { a0 in b.map { b0 in a0 + b0 } }


方法4: もはや何のために Optional を使ってんだか


Swift

let r4: Int? = a == nil ? nil : b == nil ? nil : a! + b!



アプリカティブスタイル

Haskell なら アプリカティブスタイル で次のように書けます。


Haskell

(+) <$> a <*> b


これを Swift でやってみましょう。

まずは、 <*> 演算子を実装します。 Haskell の Maybe<*> と同じですね。


Swift

infix operator <*> { associativity left }

func <*><T, U>(lhs: (T -> U)?, rhs: T?) -> U? {
switch lhs {
case .None:
return nil
case .Some(let transform):
return rhs.map(transform)
}
}


さて、問題は <$> です。 Swift の関数はカリー化されていないのでどうすれば良いでしょうか。

とりあえず、 2 引数の関数に特化して作ってみましょう。なお、 Swift では $ の文字を演算子に含むことができないので、ここでは <$> の代わりに <^> としています。


Swift

infix operator <^> { associativity left }

func <^><T, U, R>(lhs: (T, U) -> R, rhs: T?) -> (U -> R)? {
return rhs.map { t in { u in lhs(t, u) } }
}


2 引数関数に部分適用し、 1 引数関数に変換しているところがポイントです。

早速これらを使ってアプリカティブスタイルで計算してみましょう! Swift の演算子は関数なので(例えば、 2 + 3(+)(2, 3) と書けます)、 + を適用してみます。 (+) となっているのは、 + を演算子ではなく関数として認識させるためです。


Swift

let a: Int? = 2

let b: Int? = 3

let r5 = (+) <^> a <*> b
println(r5) // Optional(5)


なんと! Haskell のアプリカティブスタイルそのままに計算できました!!

しかし、今は <^> を 2 引数関数限定で実装しているので、このままでは 3 引数以上の関数に適用できません。 Swift の関数はカリー化されていないので仕方ありません。 4 引数か 5 引数くらいまで <^> を実装しておけば実用上問題ないのではないでしょうか。


Swift

// 3 引数関数用

func <^><T, U, V, R>(lhs: (T, U, V) -> R, rhs: T?) -> (U -> V -> R)? {
return rhs.map { t in { u in { v in lhs(t, u, v) } } }
}
// 4 引数関数用
func <^><T, U, V, W ,R>(lhs: (T, U, V, W) -> R, rhs: T?) -> (U -> V -> W -> R)? {
return rhs.map { t in { u in { v in { w in lhs(t, u, v, w) } } } }
}

func add3(a: Int, b: Int, c: Int) -> Int {
return a + b + c
}
func add4(a: Int, b: Int, c: Int, d: Int) -> Int {
return a + b + c + d
}

println(add3 <^> a <*> b <*> c) // Optional(10)
println(add4 <^> a <*> b <*> c <*> d) // Optional(17)


Swift は Optional Chaining も書きやすいし、アプリカティブスタイルもあれば Optional がいい感じに使えそうです。


<^>を一つにまとめる

それにしても、 <^> をいくつもオーバーロードするのは不細工です。

より汎用的なカリー化のための関数を用意して、 <^> の実装は一つで済ませましょう。


Swift

func curry<T, U, R>(f: (T, U) -> R) -> T -> U -> R {

return { t in { u in f(t, u) } }
}
func curry<T, U, V, R>(f: (T, U, V) -> R) -> T -> U -> V -> R {
return { t in { u in { v in f(t, u, v) } } }
}
func curry<T, U, V, W, R>(f: (T, U, V, W) -> R) -> T -> U -> V -> W -> R {
return { t in { u in { v in { w in f(t, u, v, w) } } } }
}

func <^><T, R>(lhs: T -> R, rhs: T?) -> R? {
return rhs.map { lhs($0) }
}

let c: Int? = 5
let d: Int? = 7

println(curry(+) <^> a <*> b) // Optional(5)
println(curry(add3) <^> a <*> b <*> c) // Optional(10)
println(curry(add4) <^> a <*> b <*> c <*> d) // Optional(17)


curry がはさまる分だけ面倒ですが、カリー化は <^> に限らず使えるより汎用的な処理なのでこちらの方が良さそうです。これなら Haskell の


Haskell

pure (+) <*> a <*> b


も次のように再現できます。


Swift

.Some(curry(+)) <*> a <*> b


curry さえ N 引数分実装しておけば、 Swift でもっと関数型言語っぽいことができて便利そうですね。


ライブラリ化

せっかくなので 15 引数まで対応して ApplicativeSwift というライブラリにしてみました。 Array についても実装したので次のようなこともできます。

println((*) <^> [1, 2] <*> [3, 4]) // [3, 4, 6, 8]

println([curry(+), curry(*)] <*> [1, 2] <*> [3, 4]) // [4, 5, 5, 6, 3, 4, 6, 8]


以上、「すごいHaskell」を読んでてアプリカティブスタイルが便利そうだったのでやってみました。


追記 (2015.08.06)

Swift で Functional Programming をするには次のライブラリが便利です。