これはなに?
圏論とか全然意味わかんない僕が出会ったアプリカティブと、僕でも分かったアプリカティブの利用法
難しいことはなにも考えずにただアプリカティブを使うことを目的としています。
説明に使うフレームワーク GitHub: thoughtbot/Runes
記号などはこのフレームワークに準じます。
これだけは知ってないとダメかも
CarthageまたはCocoaPodsまたはSwiftPackegeManager
上に書いたGitHub: thoughtbot/Runesを使うにはいずれかの知識があったらすごく楽です。
カリー化(というか関数を返す関数)
2つの引数を取る関数があります。
func printN(_ count: Int, _ str: String) -> Void {
for _ in 0..<count {
print(str)
}
}
この関数を引数を一つずつ取るようにしたい。
普通ならこの関数の呼び出しは
printN(10, "ふつうです")
のようになりますが、これを
printN(10)("ふつうじゃない")
のようにしたいということです。
こうなります。
func printN(_ count: Int) -> (String) -> Void {
func result(str: String) {
for _ in 0..<count {
print(str)
}
}
return result
}
難しく見えるかもしれませんが、はじめにあげた関数と見比べると意味が分かると思います。
複数の引数を取る関数を一つの引数を取る関数に変換することをカリー化と呼びます。
なぜなんでしょうね? リンゴとはちみつが入ってるんでしょうか?
ジェネリクス
モナドとかアプリカティブとかは型をジェネリクスで扱うのが普通です。
ジェネリクスを使うことをカッコよく言うと抽象化とか一般化といいます。いまいちピンと来ませんね。
上のprintN
をジェネリクスを使ったのもに書き換えます。
func printN<T: CustomStringConvertible>(_ count: Int) -> (T) -> Void {
func result(value: T) {
for _ in 0..<count {
print(value.description)
}
}
return result
}
ジェネリクスのメリットは上の例では二つ目の引数(のように見える返される関数の引数)に大体何でも渡せるようになるということです。
printN(10)("じぇねりくす")
printN(3)(5)
printN(999)([0, 1, 2])
どれも期待通り動きます。
アプリカティブ
ズバリいきましょう。アプリカティブとはつまりこういうものです。
f <^> a1 <*> a2
はっきり言いましょう。
意味が分からない
アプリカティブってなに?
変な記号のことをアプリカティブと思いがちですが、アプリカティブは上の例でいうとa1
とa2
のことです。
変な記号と一緒に使うことが出来る型をアプリカティブといいます。
以下では、GitHub: thoughtbot/Runesを使ってOptionalをアプリカティブとして扱います。
GitHub: thoughtbot/Runesがなんか魔法みたいなことをしてOptionalがアプリカティブになってます。
アプリカティブを使うとどうなるの?
先に作ったprintN
に渡す値がOptionalであった時のことを考えましょう。
つまりこういう時です。
let count: Int? = 10
let value: String? = "あぷりかてぃぶ"
直接使うことは出来ませんから、何か考えないといけません。
例えばこうでしょうか?
switch (count, value) {
case let (c?, v?): printN(c)(v)
default: ()
}
あるいは、こうでしょうか?
count.map { c in value.map { v in printN(c)(v) } }
いずれも正しく動きますが、読みにくいです。
では、例の変な記号を使ってみましょう。
printN <^> count <*> value
なんだこれ? 読みやすい!?
変な記号を挟んでアプリカティブであるOptionalをprintN
の引数の順番に並べただけで正しく動くのです。
これこそがアプリカティブの目指すところです。
もっと使ってみよう
printN
がどうなっていたのかもう一度見てみましょう。
func printN<T: CustomStringConvertible>(_ count: Int) -> (T) -> Void
一つの引数を取って、一つの引数を持つ関数を返す関数でした。
では、この形を守って別の関数を作ってみましょう。
数と何かを受け取ってその数分の何かを要素とするArrayを返す関数を作ります。
引数の順番が逆ですがArray(repeating:count:)
と同じことをします。
func makeArray<T>(_ count: Int) -> (T) -> Array<T> {
func result(_ value: T) -> Array<T> {
return (0..<cout).map { value }
}
return result
}
これをpintN
の代わりに使ってみます。
let array = makeArray <^> count <*> value
array.map { print($0) }
// prints ["あぷりかてぃぶ", "あぷりかてぃぶ", "あぷりかてぃぶ", "あぷりかてぃぶ", "あぷりかてぃぶ", "あぷりかてぃぶ", "あぷりかてぃぶ", "あぷりかてぃぶ", "あぷりかてぃぶ", "あぷりかてぃぶ"]
すばらしい!
ジェネリクスを使ったほうが汎用性が高まりますが、特定の動作であればジェネリクスを使わなくても全く問題ありません。
func appendString(_ str1: String) -> (String) -> String {
return { str2 in str1 + str2 }
}
let str1: String? = "アプリカティブ"
let str2: String? = "簡単なのでは?"
let str = appendString <^> str1 <*> str2
str.map { print($0) }
// prints アプリカティブ簡単なのでは?
アプリカティブ簡単なのでは?
もう一つの変な記号
アプリカティブに対して使えるもう一種類の変な記号があります。これを見てみましょう。
順番と結果
次のような関数を作ることを考えます。
2つのOptionalを返す関数があり、これらを順番通りに呼び出します。
1つ目の関数がnilを返したら2つ目の関数は呼び出さずnilを返します。
結果として1つ目の関数が返す値を返します。
まずは普通に書いてみましょう。
func getFirstValue() -> String?
func getSecondValue() -> String?
func getValue() -> String? {
guard let firstValue = getFirstValue() else { return nil }
_ = getSecondValue()
return firstValue
}
簡単でしたね。ではこれを変な記号その2を使って書いてみましょう。
func getFirstValue() -> String?
func getSecondValue() -> String?
func getValue() -> String? {
return getFirstValue() <* getSecondValue()
}
1行!!
使う値を2つ目の関数の値とする場合は普通の書き方では
func getValue() -> String? {
guard let _ = getFirstValue() else { return nil }
return getSecondValue()
}
アプリカティブなら
func getValue() -> String? {
return getFirstValue() *> getSecondValue()
}
やっぱり1行!
<*
、*>
の向きが利用する値であると見るとすごく分かりやすいですね。
ここまで単純化されるとわざわざ関数を作る必要すらなくなってしまいます。
この関数化する必要がなくなる事もアプリカティブを利用するメリットです。
アプリカティブ
変な記号はSwiftではカスタムオペレータとして定義されています。
この内容を理解するのはとても大変です。
でも、使うだけなら今すぐ誰にでも使えます。
GitHub: thoughtbot/RunesではOptionalだけでなくArrayもアプリカティブとして使える魔法がかかっています。
みなさんもなにも考えずにアプリカティブを使ってみましょう。
おまけ
ファンクタの変な記号
先の例をもう一度見てみます。
let str = appendString <^> str1 <*> str2
str.map { print($0) }
最後の一行が何か残念です。
変な記号を作って残念さを取り除きます。
// 簡単にするため RunesApplicativePrecedence を借用
infix operator <&> : RunesApplicativePrecedence
func <&> <T, U>(lhs: Optional<T>, rhs: (T) -> U) -> Optional<U> {
return lhs.map(rhs)
}
Optionalのmap関数をオペレータにしただけです。
この変な記号を使うと次のように書けます。
appendString <^> str1 <*> str2 <&> { print($0) }
見やすくなりました。
オルタナティブ
アプリカティブとは別にオルタナティブと呼ばれるものもあります。
なぜ呼び名が違うのかは偉い人に考えてもらって僕たちはおいしくいただくだけにしましょう。
let left: String? = nil
let right: String? = "OK"
left <|> rigt <&> { print($0) }
// prints OK
let left2: String? = "OOOKKKKKK!!!!"
left2 <|> right <&> { print($0) }
// prints OOOKKKKKK!!!!
これは見たらすぐ分かりますね。 左がダメなら右、です。
ほかにも
他にもいろいろ変な記号があったり面白い使い方があったりします。
興味があったら調べて見てもいいかもしれません。