Swift Advent Calendar 2015 で勉強してみる(3日目)
Swift プログラミング
Swift Advent Calendar 2015 が暖かい感じに盛り上がっていて好きだったので、自分もこれを眺めながら Swift をあれこれ勉強してみることにしました。
その3日目です。
そこで、開催されている4つの Swift Advent Calendar の内容を拝見して、そこからいろいろ浮かんだことを綴ってみようと思います。
- Swift Advent Calendar 2015
- Swift その2 Advent Calendar 2015
- Swift その3 Advent Calendar 2015
- Swift(一人) Advent Calendar 2015
Swift Advent Calendar
Swift Advent Calendar の3日目は kitasukeさん の Swiftを書く時に気をつける小さな違い というお話でした。
ライブラリをとして公開するためのコードを書く中で感じた Swift コードを書く上で気にしたいことを記したコーディング規約的な内容です。
触れられている内容
この Advent Calendar 記事内では次の事柄について考察されていました。
- どういう方針で Access Control の指定を決め、どうコードを書いたらいいか
- クラスの final 指定とその理由
- プロトコルの命名規則
- 仕様変更時の @available 対応
- 値付き列挙型を扱うときのコードの記載方法
- switch によるパターンマッチと if case によるパターンマッチの使い分け指針
- if と guard の使い分け指針
- map 関数と forEach 関数の使い分け指針
- for in 構文と forEach 関数の使い分け指針
これらの中の幾つかから、思ったことを少し綴ってみます。
@IBOutlet weak private var について
Advent Calendar 記事内の Access Control で紹介されていた次のコードについて補足しておきます。
@IBOutlet weak private var button: UIButton!
他の行に比べてここだけ急に複雑になっていますけど、この行には主張したいことが幾つか含まれているため、このように複雑になっていると思われます。
そのコントローラーだけが使うボタン
まず、たとえば UIViewCOntroller
に UIButton
を配置した時に そのボタンはコントローラーからコードで操作する必要はあるけれど、ボタンの名前等をコントローラーの外側から操作できる必要はない
という状況はよくあると思います。
Objective-C の頃であれば UIButton にはコントローラーの外からもアクセスできるけれど、外からは使わないようにする
みたいな運用ルールで対応していた訳ですけれど、Swift からは private
をつけることで、それが記載されたファイル外から実際にアクセスさせないことができる、という主張が込められていると思われます。
そして、それと併せて @IBOutlet さえ付いていれば private 指定がされていても、インターフェイスビルダー上でボタンをそれに連結できる ということも主張されていると思われます。
外部からの予期しないコントロール操作を防ぐ
このような方法は自分は『できないもの』と思い込んでいたんですけど、実際に試してみたところ private
を指定したボタンをちゃんとインターフェイスビルダーで連結してコードで操作することができました。
同様に @IBAction でも private
を指定して試してみたんですけど、少なくとも軽く試してみた限りでは問題なく動作してくれる様子でした。
これなら設計上予期しない 外部からのコントロールの直接操作 を防止することができて、かなり良い感じかもしれません。
プロトコルの命名規則について
Advent Calendar 記事内の Access Control で使われている『状態』みたいな言葉が指すものが何か自分は掴めなかったので、記事とは少し独立して、それを見て思ったことを補足的に自分も綴ってみることにします。
まず記事内にもある通り、Swift の標準ライブラリにあるプロトコルを眺めていると、名前の付け方に -Type
というものと -able
という2つの種類があるのに気が付きます。
-Type
まず -Type
についてですけど、これは そのプロトコルを準拠した型がどのような型として振る舞うか
に着目して設計されたプロトコルのように思えます。
たとえば このプロトコルに準拠した型は if などの評価結果の値として使える
ことを意味する BooleanType
ですとか、たとえば このプロトコルに準拠した型は、内部に複数の内容物を持って扱える
ことを意味する CollectionType
ですとか。
そんな風に それはどんな性質を持つ型なのか
を表すものが、この -Type
という名前で表現されているようです。
-able
型の性質を説明すること自体はプロトコルのそもそもの役目とも言えて、どのプロトコルも最終的にはそうなるんじゃないかと思うんですけど、その中でも特に 何かの能力を潜在的に秘めている
みたいなことを意味する時には -able
という名前が付けられるようです。
たとえば この型は同じ型のインスタンス同士で比較可能
というような時は Equatable
ですし、たとえば インスタンス間の距離を取ったり、別の位置へ移動できるようにして、範囲を表現できるようにする
ことを意味するものなら Strideable
です。
感覚的には、先ほどの -Type
が 型に着目して、型そのものの性格を表す
ものだとしたら、この -able
は 外側から型を見て、その型から引き出せる能力を表す
みたいな違いになるのでしょうか。
同じく -able
系統の中には -Convertible
という名前が付けられたものもあって、たとえば StringLiteralConvertible
なら このプロトコルに準拠した型は、文字列リテラルから自身の型に変換することができる
みたいな、その型が別のものに変換可能なことを意味するものもあったりします。
if case について
Advent Calendar 記事内の Access Control で触れられている if case
パターンマッチのおかげで、列挙型がだいぶ使いやすくなりましたよね。
それもあって逆にこの記事内にもあるように switch
と if case
のどちらを使うかで迷う機会も出てきたわけですけれど。
if case の注意どころ
これらを使い分ける上で、自分が思う意識したいところを挙げてみます。
まず 網羅性を捨てても問題ないか
です。Swift では enum
を使って どの値を取り得るか
を列挙して、それを switch
で使うことで すべての場合が網羅されていることを保証する
仕組みが備わっています。
これがかなり大事なところで、このおかげで想定し忘れがあってもコンパイラーがエラーを出してくれて気付けるのですけど、これが if case
を使うとそういった恩恵は得られなくなります。
たとえば、列挙型 Variant
があったとします。
enum Variant {
case Number(Int)
case String(Swift.String)
case Empty()
}
このとき、たとえばこの Variant
型に 内容を表示するのに使う文字列を取得
できる description
プロパティを定義したとき、次のように String
の場合を想定し忘れても、ビルド時にエラーになって気づくことが可能です。
var description: String {
switch self {
case Number(let value):
return "\(value)"
case Empty:
return ""
}
}
ところが、これを if case
を使ってうっかり String
のケースを想定し忘れたりすると、エラーも起きず、予期しない結果を取得できてしまう可能性が出てきます。
var description: String {
if case .Number(let value) = self {
return "\(value)"
}
else {
return ""
}
}
そのため、原則的には switch
を使うのが安心な使い方に思います。
特に、列挙子の数が2つとか3つくらいの場合だと if case
が適切そうに見える機会も多かったりするかもしれないですけど、次のような その列挙子かそれ以外の列挙子かで判定することがテーマの機能
の場合を除いて、すべて switch
で処理するくらいの気持ちの方が、結果として扱いやすいコードになってくれそうな気がします。
var numberValue: Int? {
if case .Number(let value) = self {
return value
}
else {
return nil
}
}
網羅性が特に効果的に働く場面は、将来 enum
に項目を追加した時だと思うのですけど、この例のようなコードであれば 将来どんな列挙子が追加されても、ここの処理には決して影響しない
ので、こういう時に if case
を使うとコードが良くなる気がします。
そしてそんな場面はかなり少ないので、基本的には if case
を使う場面はかなり限られてくるのかなと感じています。
ちなみにこのコードは switch
を使って普通にシンプルに書くこともできます。
var numberValue: Int? {
switch self {
case .Number(let value):
return value
default:
return nil
}
}
この場合と if case
を使う場合はどちらが良いのでしょうね。
意味的には全く同じですし、網羅性チェックの恩恵がなくなるのも同じ、コードの長さもそれほど変わらないので、自分だったらその時々の気分でどちらを選ぶか変わってしまいそうな気がします。
そういう面から考えてみると、もしかして if case
の利用用途はさらに限られて、今までの話に加えてさらに それ以外の場合は何も処理が必要ないとき
みたいな場合、要は if case
があってそれに対する else
が不要みたいなときにコンパクトに読めるコードが書ける、みたいな使用感になってくるかもしれません。
Swift その2 Advent Calendar 2015
Swift その2 Advent Calendar 2015 の3日目は morizotterさん の Swiftの非同期処理を簡単に書けるBrightFuturesをコード例を多用して解説する というお話でした。
Swift での非同期処理といえば、これまでの Objective-C
からの流れ的には NSThread
とか performSelectorInBackground
とか幾つか選択肢がありますけど、その中でも GCD
が一般的なのかなと思いながら使っていたんですけど、何やら Thomvis/BrightFutures
というライブラリも選択肢にあったりするみたいです。
この Advent Calendar 記事内では、そのライブラリがどんな風に使えるのか、実際のコードを踏まえて解説されています。
高レベルなライブラリの様子
そんな記事のコードを眺めてみると、まず最初に自分が想像したものとは違って、もっとアプリケーションに近い部分を表現できるライブラリみたいな印象でした。
どうやら GCD
みたいなものではなく ishkawa/APIKit
のような、たとえば Web API にアクセスする
みたいな非同期処理で威力を発揮するライブラリのようでした。
BrightFutures に窺える特徴
この Advent Calendar 記事内の 基本的な説明 のところを見ると BrightFutures ライブラリの特徴的なところが窺えてきます。
特に良さそうだったのが、非同期処理を連ねて最後に onSuccess や onFailure で扱えるところでした。このときに flatMap
を適用することで不要なネストを解消できるらしいところが、ついつい深くなりがちな通信処理をスマートに書けて便利そうです。
flatMap
ところで flatMap
って、こういう場面で応用できるんですね。
前回の mo_to_44さん さんが Advent Calendar で Swiftのmap, filter, reduce(などなど)はこんな時に使う! で話されていたのを思い出しました。
同じように自分自身も flatMap
のことを捉えられていないのですけど、その使い方のアプローチではなく概念的なところを弁えられるようになって初めて、こんな風に flatMap
を適切に活用できる仕組みを設計できるようになれるのかなって感じがしました。
Promise から Future ?
そんな風に読み進めていて、記事内の Promise から Future を作る に差し掛かったところで、読み進められなくなりました。
タイトルに出てきた Promise
と Future
が何を指すのか知らなかったので、それからそれを作ると言われても、何から何を作るのかが汲み取れなかったのが理由です。何かの概念的なところで登場する言葉なのでしょうか。
コードや説明の流れを見ると、何となく Promise というものは、成功か失敗かを必ず返して次に繋げることを約束するみたいな感じに見受けられるのと、Future はその将来の成功か失敗かが返ってくるのを見据えて今からスタートからエンドまでのフローを完成させておく仕組みみたいに見受けられました。
その想像が合っているかはわかりませんけど、なんにせよその違う2つの性質を flatMap
で表現する中で、その違いを中和してひとつに繋げることを実現する仕組みのあたりが開設されているように窺えました。
andThen からの感想
そして andThen
の説明に差し掛かって なるほど Optional の map 関数や Optional Chaining みたいで面白いな
と思ったんですけど、それならもしかして最初の flatMap
も flatMap する
しか意味しないメソッドよりも、このコードみたいに何かをすることが伝わってくる名前のメソッドを用意した方が読みやすいような気がしたりしました。
Swift 1 の頃みたいにグローバル関数でないとジェネリックを活かしにくい頃なら flatMap
が適切なのかもしれないですけど、Swift 2 ならプロトコルに機能を閉じ込められるので際限なく関数が増えてわかりにくくなるみたいなことも起きないので、メソッド名が(この andThen
みたいに)役割をピンポイントで示してくれていた方が好ましいかもしれないように思います。
もっともそんな感想は、単に flatMap
がなんなのかというそもそものところを知らない自分だけの感想なのかもしれませんけれど。
詳しく丁寧にまとめられている印象
並列処理についてほとんど疎い自分でも、こんな風にして読み進めながら考えを進めていけるくらいに、詳しく丁寧にまとめられていて読みやすい印象がしました。実際のコードがたっぷりあるのが、言葉だけでは読みきれない部分を汲み取る助けになって、さらに良い印象でした。
この Advent Calendar 記事内ではさらに もう少し深く と題して、基本を理解した次のステップアップができるように構成されているのも嬉しいところでした。
map とか flatMap とか
余談ですけどそんな風に読んでいた時に、今まで自分が Swift 標準ライブラリを見る中で 微妙な違い
くらいに感じられていた map
と flatMap
とが、この Advent Calendar 内の flatMap を使ってもっと直列に
と map で値を変更する
を読んだ時に、その差がはっきり出ていてとっても興味を覚えました。
この map
と flatMap
については、まだちゃんとした概念は捉えていないながらも、先日に参加した 第64回 Cocoa勉強会関西
で @royskimJPさん
が話されていた『OOP で説明するMonad』を聞いたところだったので、その記憶と照らし合わせてみたときに、概念的な両者の違いが体感として少しばかり得られたような心地がしました。
おなじみの品揃え感
他にも意識に止まったのが、記事内の もう少し深く
のところで map
, zip
, filter
, sequence
といった、いわば Swift でもお馴染みのキーワードが登場するところでした。聞きなれない recover
も、動きに着目すれば Optional の nil 結合演算子 (??) と捉えればこれらの仲間に入れられそうです。
Swift にある機能を真似たと捉えることもできますけれど、むしろ個人的には同じ思想に基づいて新たに生まれたもののように感じられて、なんとなく視野を広げてもらえたような心地がしました。はっきりと捉えるのはまだ先ですけれど、とても良い経験になってくれそうな予感がします。
日がな漠然と聞いていることと連想させて
そしてもしかして、関数型プログラミングってこれのことを言っているのかなって思えてみたり。
たしかに並列処理を行う上で、この手法は理想的な流れを作れるように見えました。そしてオブジェクトとか状態が不都合という話をよく聞くことを断片的に思い出して、もしかしてこの手法を区別なく全てにおいて適用したいみたいな流れになってるのかなとも思えてみたり。
そして、これと関係ある事柄なのかまだ判断できないですけど、関数型プログラミングとセットで聞く気がするオブジェクトや状態との不和について、普通に使えばこの BrightFeatures 上にたとえば UILabel を流すみたいな場面ってなさそうな気がしますし、流れに乗せる過程の中で切り分けが曖昧になることもなさそうに思えます。
もっとも今の自分の知識の範囲での感覚なので、今後知識が身につく中でどう変わるかはわかりませんけど、今のところはまだ、世界観にオブジェクトや状態が存在していて何か不都合になることも無さそうに感じられました。今までどおり 値は値型で表現をして、オブジェクトは値を制御するためのもの と捉えれば、普通なコードが書けそうな気がします。
そして今回の話を通して、この BrightFeatures で採られている手法が身についた時には、今とは全く違う趣向のアプローチでコードを書くようになっていそうなことも、合わせて感じさせられました。
Swift その3 Advent Calendar 2015
Swift その3 Advent Calendar 2015 の3日目は 335gさん の Functors in Swift というお話でした。
趣旨としては Haskell に登場するさまざまな『ファンクター』を Swift に定義してみようというものです。
趣旨
最終的には理論でちゃんと把握しないと応用できないとは思うのですけど、最初から理論で全てを把握することはなかなか難しいので、具体的なコードで書かれていると理解の助けや認識度の確認になって嬉しいです。
しかもそれが今回のファンクターみたいな関数型プログラミングの概念だと、具体的なコードが Haskell で書かれていることが多くて Haskell を知らないとなかなか参考にするのも困難でした。
それが今回は Swift で定義してみよう
という話で、とっても素敵な趣旨に感じました。
取り上げられているファンクター
ファンクターには性質によって幾つか種類があるみたいで、この Advent Calendar 記事内では次のファンクターに触れられていました。
ここで早速感動したのですけど、登場するファンクターの名前を見て、先日に開催された クラスメソッド株式会社 様の iOS 9 週連続 Bootcamp 7週目 で @usrnameu1さん が話されていた『共変 (covariant)』と『反変 (contravariant)』と同じ言葉が出てきました。
今回の話とそれとが同じものを指すかどうかは分かりませんけど、概念的には同じような性質を表現するものではないかと思います。
そして先日の話を聞く限り、共変や反変は Swift のプロトコルを完成させる上で必要そうな気がしましたし、たしか Objective-C のジェネリクスにも __covariant
とか __contravariant
みたいな言葉が登場していたような気がして気になる言葉だったのでした。
そんな概念的なところをファンクターの視点からも窺うことができそうで、これはとても楽しみです。
図と解説がわかりやすい
Advent Calendar 内の記事を読み進めると、それぞれのファンクターについて導入の部分から丁寧に紹介されるばかりか、イメージ図がアスキーアートで添えられていて分かりやすいです。
最初に Haskell のコードが紹介されて、自分にはそこに限っては複雑さを増す感じですけど Haskell 文法を知っている人にとっては嬉しいでしょうし、説明を終えた後の Swift コードは理解の復習になってとても嬉しい構成でした。
各ファンクターの説明がコンパクトに上手くまとめられてて、サクッと読めるところも嬉しいです。
それでもさすがに目を通せば全てが解るみたいな易しい概念ではないみたいですけど、ともあれ関数型プログラミングが気になる Swift ユーザーはぜひぜひこの Advent Calendar 記事に目に通しておくと嬉しいことがありそうでした。
map がファンクターとして独立してないことと Array を返すこと
そんな Covariant Functor の説明のところで Swift では map が Functor として独立していない ことと map の戻り値が Array になっている ことについての言及がありました。
このテーマは、前者は自分が Swift の目指すところが関数型言語とは一味違うのではないか と感じていた理由のひとつで、後者は以前に開催された Swift 2 (& LLDB シンポジウム) の中でも盛り上がった話題だったのを良く覚えてます。
制約
そしてこの Advent Calendar 記事内で 今の Swift 2.1 では理想の形に表現できない みたいなことが記されていて、とても興味を覚えました。
これが他所からときどき聞こえてくる(関数型言語から見た)Swift の制約 のひとつなんですね。そしてその制約が、もし言語が敢えて敷いた制約の中の世界であれば何も不思議は感じないですけど、もし単に共変や反変の仕組みが間に合っていないだけだとしたら、自分もそれなりの考え方の転換が必要になるかもしれません。
そして ここの解決を図ると default implementation が機能しなくなる という面白い考察が添えられていて、言語が選んだ制約なのか、解決困難な制約として残ってしまったのか、その分かれ道が興味深いところでした。
ここでいう default implementation というのはプロトコル拡張のことだと思うので、そうだとするとこれを廃止したらまた Swift 1.x の頃の大域関数の時代に戻りそうですし、そこまでしてファンクターという存在を独立させる価値があるのか、そんな風に考えてみるのも楽しいです。
それと CollectionType
の map
の戻り値が Array
になることについては、もしかすると普通に戻り値を Self
にする手立てもないとは言えないんじゃないかな、とも思えてみたり。
そんなところに想いを馳せてみるのも面白そうかもしれません。
Bifunctors
Advent Calendar 記事内の Bifunctors で、Swift 1 の当初から何かと世間で耳にする Either という言葉も出てきました。
なるほど、そういう概念の上で Either が存在していると捉えられると、扱いもまた違った味わいを見せることになりそうですね。
Swift(一人) Advent Calendar 2015
Swift(一人) Advent Calendar 2015 は全日を shimesabaさん が埋め尽くすという素晴らしさですけれど、その第3日目は 普段なにげなく書いている[unowned self]の意味を調べる というお話でした。
自分も Swift を学び始めて間もなく出会った weak
と unowned
ですけれど、たしかに表向きの動きが分かりやすすぎて、ついつい細かいところを知らないまま使ってしまいがちな機能かもしれません。
キャプチャリストの性質について
この記事内では、問題となり得る具体例が紹介されて、そこから基本的な対策方法と、その中で使われている書式についての細かな解説があります。
そこに記載されているように、値型をキャプチャするかしないかや、キャプチャするのが値型かオブジェクト型かで振る舞い方が変わってきたりするので、今まで 循環参照を回避するために、なんとなく使ってた みたいな人は眺めてみると良いことありそうです。
unowned の種類
ところで unowned によるキャプチャには次の2種類の方法があります。
- unowned(safe)
- unowned(unsafe)
何も指定しないで普通に unowned とすると unowned(safe) が指定されたのと同じになるようです。
2つの unowned の特色の違い
前者の unowned(safe) では、キャプチャした変数が内部的には ref_to_unowned (@sil_unowned) という扱いになるようで、内部ではクロージャーの中ではじめに strong_retain_unowned
が呼ばれ、そして終わりに unowned_release
が呼ばれる様子でした。
対して unowned(unsafe) では、キャプチャした変数が内部的には ref_to_unmanaged (@sil_unmanaged) という扱いになる様子でした。これは Objective-C でいう assign
とか unsafe_unretained
と同じ扱いらしいです。
unowned(safe)
これによって生じる変化は、どちらもキャプチャしたオブジェクトが nil
にならない前提で使うものなのでクロージャーを実行する時点でそれが解放されているとダメな点は変わりませんけど、前者の unowned(safe) では strong_retain_unowned
が呼ばれる都合か、もし解放されていた場合は Swift の _swift_abortRetainUnowned
という機能がエラーを検出して止まるようになっていました。
そこまでは把握できたのですけど、この unowned_retain
をなんのために使っているかまではわかりませんでした。名前や機能の説明からして retain しそうな感じがしますけど、それでどんなメリットがあるかはわかりませんでした。
Retain Counter とは言っても、どうやら従前のとは別系統の Unowned Retain Counter というのがありそうな感じです。
unowned(unsafe)
ちなみに unowned(unsafe) の場合は昔ながらに EXC_BAD_ACCESS で落ちることになる様子です。しかも解放後もアクセスできたりすることもあるので、使わない方が無難そうです。
つまりこれが unowned(safe) で管理している時の strong_retain_unowned
による成果の顕われなのかもしれないですけど、今のところはエラーが起きた時にどちらも分かりにくくて、それが unowned(safe) で管理されていることが大きなメリットにつながるかどうかは微妙な印象でした。
ただ、メモリー周りの管理をしていることは確かなはずなので、不正アクセスの防止とかシステム動作の安定性とか、直接的には見えないところの何らかの効果はあるんじゃないかと期待してみたりしてます。
そして将来的にはデバッグ周りも強化されれば、もしかするとこれが功を奏して、よりデバッグしやすい情報を知らせてくれることにも期待しつつ。