MERY のサーバーサイドエンジニアの藤原です。
今回は、MERYのiOSアプリには使用していないのですが、一部で話題のSwiftzについて調べてみました。
Swiftzとは
他の関数型言語によくある機能を、Swiftでも使えるようにするライブラリです。
Swiftにも関数型言語っぽいmapやreduce、filter、flatMap等がありますが、例えばEitherはまだありません。
そういった、「他の言語だったらこう書けるのに」を解消してくれるライブラリです。
Swiftzの利点と欠点
利点としては、
- 上手くやりたいことと噛み合うとコードを短くスッキリと書ける
- コードの再利用性を上げやすくなる(部品を書きやすくなる)
- Swift3.0で追加されそうな機能を先取りして使える
- 他の関数型言語を覚えやすくなる
等があると思います
欠点としては、
- SwiftのupdateにSwiftzが追従するのにタイムラグが起きやすい (他のライブラリと比べて、言語仕様の影響を受けやすい)
- 学習コストが高い (演算子が直感的にわかりにくい人が多そう)
等がありそうです。
使ってみる
Swiftzで拡張される機能は、ざっくり分けると型と、演算子です。
全ては紹介しきれないですが、それぞれいくつか例を上げて紹介したいと思います。
型
Either
まずは利用機会の多そうな、Eitherを使ってみます。
ObjectiveCでは
NSData* data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
のように、成功時につかう値(この場合はdata)と失敗時に使う値(この場合はerror)を同時に受け渡したいために、 NSErrorの参照を渡すパターンが多く出てきます。
そこを綺麗に書けるようにSwiftではtry-catch等の構文が追加されたのですが、"通信エラーの場合はキャッシュに入れる処理をスキップして、エラーメッセージをHUDで表示する"のような用途だとtry-catchは扱いづらいと思います。 そういうった時に活躍するのがEitherです。
Eitherは、「2つの型のうち、どちらかの値が入っている」ことを表現するための型です。 Either<Int,String>であれば、IntかStringの値が入っています。 多くのコーディング規約では、左側に失敗時に使う値を、右側に成功時に使う値を入れることになっています。 *1
例として、通信に成功した場合は受信した文字列を、失敗した場合はhttpステータスをintで入れる想定のEitherを作ってみます。
let ok :Either<Int,String> = Either.Right("connected") let ng :Either<Int,String> = Either.Left(404)
使うときは、onLeftとonRightで取り出します
func ToString(httpResult:Either<Int,String>) -> String { return httpResult.either( onLeft: { return "[http error] \($0)"}, onRight: { return "[recive message] " + $0} ) }
成功時も失敗時も同じ型の変数で受け渡しできるので便利ですね。
getOrElse
既存の型に対しても、使いやすくするExtensionが入っています。 例として、getOrElseを紹介します。 SwiftのOptional型のExtensionで、中身がnilの場合は引数で与えた初期値を返すメソッドです。
var someString:String? = "hello" var noneString:String? = nil print(someString.getOrElse("not found")) // hello print(noneString.getOrElse("not found")) // not found
if-let else で書くより簡潔に書ける場面がありそうですね。
演算子
Swiftzで用意されている演算子は
https://github.com/typelift/Operadics#operators
にまとめられています。 *2
いくつか使ってみましょう。
compose / apply / thrush
composeの演算子は•です。複数の関数を1つの関数にまとめるための演算子です。
let addHeader: String -> String = { "Your input " + $0 } let addFooter: String -> String = { $0 + " in Int"} let toString : Int -> String = { "\($0)" }
という3つの関数があるとして、compose演算子を使わずにまとめると
let compose1 = {x in addHeader(addFooter(toString(x)))}
となります。
compose演算子を使った場合は
let compose2 = addHeader • addFooter • toString
という風に書き直せます。
compose1とcompose2はどちらも同じ動きをして、
compose1(3),compose2(3)は"Your input 3 in Int"を返します。
動きは同じなのですが、compose演算子を使った場合は関数が複数あっても
let compose = func1 • func2 • func3 • func4 • func5
のようにネストが深くならないので、見た目がすっきりします。
composeと似た使い方ができるものに apply や thrush があります。それぞれの演算子は、<|と|> です。
composeは複数の関数をまとめて1つの関数を返すのですが、 applyやthrushは複数の関数をまとめて適用する事ができます。
let result1 = addHeader <| addFooter <| toString <| 3 let result2 = 3 |> toString |> addFooter |> addHeader
apply(result1)とthrush(result2)は、書く順番が逆ですがどちらも同じ動きをして、やはり"Your input 3 in Int"が入っています。
iOS開発ではアニメーションのネストが深くなりやすいので、autoclosure等と組み合わせて
//compose let animationNotify = animationApply • animationFadeFadeOut • animationShake • animationFadeIn • animationBuild animationNotify(notifyView) //apply animationApply <| animationFadeFadeOut <| animationShake <| animationFadeIn <| animationBuild <| notifyView //thrush notifyView |> animationBuild |> animationFadeFadeIn |> animationShake |> animationFadeOut |> animationApply
みたいに使うと便利かもしれません。
まとめ
Swiftzは以下の様な場面で便利です。
Reactive系のライブラリと組み合わせて使うのも相性が良いと思います。