読者です 読者をやめる 読者になる 読者になる

Swiftzつかってみた

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は以下の様な場面で便利です。

  • 他の関数型言語を使ったことがあり、Swiftで同様な型/機能を使いたい
  • Swiftをきっかけに関数型言語を覚えていきたい

Reactive系のライブラリと組み合わせて使うのも相性が良いと思います。

*1:右(Right)が正しい(Right)というのが、きっかけのようです。

*2:extract演算子は、Operators.swiftを見る限り用意されていない気がします

© peroli, Inc.