第2回目の Swift 2 シンポジウムも沢山の学びが得られる良い会でした。

Swift 2 シンポジウム

今回で2回目となる Swift 2 シンポジウムに、今回はパネリストとして参加させて頂きました。

前回で大きな注目点は出尽くしたかと思っていたら、今回はさらに良質なテーマが掘り起こされて、たっぷりと楽しく議論できました。

実りもとっても大きかったです。


に開催された @k_katsumiさん 主催の勉強会『Swift 2 シンポジウム #2』にお邪魔させて頂きました。

http://eventdots.jp/event/567617

とっても楽しかった勉強会『Swift 2 シンポジウム』の第2回目が、渋谷の イベント&コミュニティスペース dots. で開催されました。

今回は準備が間に合わなくて発表枠での参加はできませんでしたけど、パネリストという枠に入れて頂いたおかげで発表者のお話を間近で聞いて議論に混ざれたおかげで、今回もたっぷり勉強できて、とても楽しい時間を過ごすことができました。

前回、第1回目に参加させて頂いたときのお話は こちら に記してあります。

議論主体の勉強会

この会の特徴はなんといっても『議論主体』の勉強会です。

それが前提に公言されてるからか、特に発表者(議論のきっかけを作る人)とパネリスト(議論に積極的に参加する人)との間に溝とかなくて、お互いが安心して疑問を投げかけられる、穏やかに異論を交し合える、そんなところがとてもいいなって思えました。

そんな空気はもしかして、主催者の人柄にも依るところなのかもしれません。


わずかに気になるところがあるとすれば、今回は前回と募集形式が違って、募集ページ自体では「発表枠」「パネリスト枠」「参加者枠」とが1つにまとめられていたところが、募集のときにほんの少しだけ障壁になった心地がしました。

発表枠はもともと敷居を高く感じる人は多いと思うんですけど、今回はパネリスト枠も主催者に別の手段で直接連絡しないといけなかったので、もしかするとそれで躊躇うこともあるのかなとか、なんとなく感じてみたりしながら、パネリスト枠に応募していた自分でした。

さておき、とにかく言えることとして、もしも誰かが次回にどの枠で参加しようか悩んだら「パネリスト」での応募をオススメしたいです。とっても楽しいですので。

ふりかえり

話がわずかに逸れましたけど、みんなの話題を振り返りながら復習してみたいと思います。

ちなみに前回の第1回目で Swift 2 の真新しい部分は広く議論された気がして、今回はどんな感じになるのかなって思いましたけど、それでも普通に懇親会の席みたいに、穏やかな話題にみんなで花を咲かせられて、とても心地の良い場所でした。

そういう風に在れたのはきっと、発表者のみんなが議論に発展しやすそうな話題を選んでくれたのも大きかったように感じます。

アイスブレイク

最初は、主催者である岸川さん(@k_katsumiさん )による、Swift 2 の beta 2 から beta 6 までに変わったところの紹介が、ざくざくと繰り広げられました。

Module 名が補完されるようになった

まず最初の注目点が、Xcode 7 beta 6 で import を書くときに、モジュール名が補完されるようになったというところでした。

自分はこれまで標準モジュールや自作モジュール以外を使うことが少なかったせいか、稀に長いモジュール名を書くときに少し気になることがあったくらいでしたけど、それでもやっぱり補完が効くのは嬉しいです。


補完できるモジュール名は、標準モジュールだけでなく、自分で組み込んだものにも対応しているところが嬉しいです。

ただ、お話の中でもありましたけど、どういう事情か一部のモジュール名は補完されなかったりするみたいです。ちなみに自分は先ほど引用したツイートでこの機能を知ったんですけど、そこでは Foundation が補完される例が紹介されていますけど、自分の手元では Foundation は補完されないみたいです。

可変長引数を途中でも使える

もうひとつ、可変長引数を引数リストの途中で使えるというのが挙げられました。

func acceptable(maxLength:Int, items:String..., separator:String) -> Bool {
    
}

acceptable(5, items: "A", "B", "C", separator: " ")

これまでは、可変長引数は最後の引数でしか使えなかったのですけど、これが beta 6 からは引数リストの途中にも置けるようになりました。

自分は、これはもしかして良い感じに使えるのではないかと思っていて、もしこれがなかったとすると、引数の途中に任意の数のアイテム取りたくなったときには、配列で受ける必要がありました。

func acceptable(maxLength:Int, items:[String], separator:String) -> Bool {
    
}

acceptable(5, items: ["A", "B", "C"], separator: " ")

引数を受けた後の処理(関数の実装)はどちらも同じですけど、関数を呼び出すときに配列リテラルを使わないといけないところが面倒に感じたりします。

その点、可変長引数であればカンマ区切りで好きなだけ指定できるのが嬉しいところです。些細な違いですけれど、記号を入力しなくて済む点と、視覚的にカッコで分断される感じがしないのが気に入ってます。

既定値を取るメソッドと可変長引数

積極的に可変長引数を使っていく上で、役に立ちそうな場面としては、まずは既定値があげられるかなと思いました。たとえば『最後の引数に既定値を与えて省略可能にする』みたいなことが可能になります。

func acceptable(maxLength:Int, items:String..., separator:String = " ") -> Bool {
    
}

acceptable(5, items: "A", "B", "C")

もっとも Swift では既定値を取る引数を末尾に置く必要はいようで『末尾に可変長引数を置いてその手前を省略する』みたいなこともできたので、既定値を活用する上で際立って役立つものでもなさそうでした。

func acceptable(maxLength:Int, separator:String = " ", items:String...) -> Bool {
    
}

acceptable(5, items: "A", "B", "C")

それでも自分的には、省略できる引数が後ろに書いてあった方が感覚的には自然な感じがするので、それと可変長引数とが両立できるところは嬉しいところです。

末尾クロージャーと可変長引数

他に、可変長引数を好きなところに置けるようになったのを活かす方法として、末尾クロージャーと合わせて使う方法もありそうです。

func applyTo(items:String..., predicate:(String)->Void) {

    items.forEach(predicate)
}

applyTo("A", "B", "C") {
    
    print($0)
}

これまでは可変長引数も末尾クロージャーも使うにはどちらも最後に置く必要があったため、どちらを使うか選ばないといけなかったですけど、これからはこうして両方を使えるようになります。

可変長引数を2つ以上は使えない

ところで、自由な場所に可変長引数を置けるようになったなら、もしかして複数の可変長引数を受けとることもできるかもと思ってやってみたのですが、残念ながらできませんでした。

func setValues(values:Int..., forKeys keys:String...) {
    
}

たとえばこのように実装しようとすると次のように指摘されます。

Only a single variadic parameter '...' is permitted

つまり、たとえば NSDictionaryinitWithObjects:forKeys:メソッド みたいなメソッドは、両方を可変長引数にはできず、従前通りの配列で実現するしかなさそうですね。

init(objects:[AnyObject], forKeys: [NSCopying]) {

}

もっとも、今回の例に限っては、キーとオブジェクトの数が一致してないと適切ではないので、独立した2引数として存在するのも不思議に思えます。

Swift ならそれよりも、タプルを使ってまとめた方がいいのかなとも思えました。

init(items:(key: NSCopying, value: AnyObject)...) {

}

NSDictionary(items: ("A", 1), ("B", 2), ("C", 3))

これなら可変長引数もひとつで済みますし、キーとオブジェクトの対も一致するので良い感じとは思うのですけど、丸括弧がたくさん出てくるところが、打ちにくさと読みにくさを感じるようにも思います。

可変長引数に配列は渡せない

それと、可変長引数を好きなところに置けるようになったのとは直接関係ないんですけど、自分がとても残念に思っているところとして『可変長引数のところには配列などのシーケンスタイプを指定できない』というのがあります。

そのため、あるメソッドの可変長引数で受け取った値を、別の可変長引数へ引き継ぐことができない感じです。

struct MyValue : ArrayLiteralConvertible {
    
    func applyTo(items:String..., predicate:(String)->Void) {
        
        items.forEach(predicate)
    }
    
    init(arrayLiteral elements: String...) {
        
        // 可変長引数は受けた時点で配列になるため、
        // 可変長引数を取る別のメソッドに渡せない。
        self.applyTo(elements) {

            print($0)
        }
    }
}

そういうときは、そもそも可変長引数ではなく配列で値を受けとるようにしたり、それか可変長引数を取るメソッドと配列を取るメソッドとを作って、可変長を取る方をコンビニエンス的に使う方法もあると思いますけど、可変長引数で暗黙的に配列に変換されるなら、配列も可変長引数に展開できたら使いやすくなるのかなと思いました。

それか、たとえば可変長引数の型を実質 Variadic<T> みたいにして Array にキャストできるようにするとか、そんな方法で可変長引数型を継続して使えるような手段ができてもいいのかなと思えました。

そもそも可変長引数の分かりにくさは?

そんな話の中で「そもそも可変長引数は分かりにくいのでは?」という意見も挙げられていました。

これについては、どうなのでしょうね。

議論の中でも取り上げられて『Swift の場合は引数ラベルもあるし、分かりにくくなることはないのでは?』という意見が出たように記憶していますけど、自分はもともとお気に入りなせいか、特に見にくくなることはないかなと感じてます。

引数ラベルによる明確化

まず、意見でも出たように、ラベル名があるので他の引数とちゃんと区別がつくというのも自分の中では大きいです。

これが C 言語の関数みたいにラベルがなかったとしたら意見も変わってくるかもしれませんけれど、今のところは可変値引数によって [ ] 記号を書かなくなることの方が、読みやすいように感じます。


特に Swift 2 からはメソッドだけでなく関数も2つ目以降からはラベル名が必要になったので、ラベル名により重みを持たせた仕様になったように窺えます。これからは Objective-C みたいにラベル名で引数をグループ化して行く流れなのかな、とも、思ってみたりしているこの頃でした。

そうやってラベル名に重きが置かれるようになれば、先ほど上で触れたように、原則的にはラベル名によって可読性が保たれるように思います。

他にも、今のところは1つのメソッドに可変長引数を1つまでしか使えないのも、たくさん使って読みにくくなるみたいな心配もなさそうです。

意図的にラベルを省略したとき

ただし、ラベル名に重きが置かれたようだとは言っても _ を指定すればラベル名をなしにできるわけで、そうしたときに分かりにくさが急増する可能性もあり得ます。

そう思って、間に可変長引数を取り、両側に同じ型の引数を取り、それらのラベルを全て無しにした関数を定義してみたところ、結果としては、少なくとも現時点では使うことができませんでした。

func make(prefix:String, _ middle:String..., _ suffix:String) -> String {
    
}

make("P", "A", "B", "C", "S")

たとえばこういう関数を作ってみたところ、関数を使おうとしたタイミングで次のエラーが発生しました。

Missing argument for parameter #3 in call

明らかに Swift 自身が引数の数を捉えきれていない感じなので、今後のリリースで改善されるかどうかは分かりません。ちなみに Swift が適切に数を捉えられないのは、型が全て同じだからとかではなくて、たとえば最後の引数の型が Int であったとしても、適切に裁くことはできませんでした。

可変長引数の次にはラベル名が必須

これを改善する方法は、今のところは少なくとも、可変長引数の次の引数にはラベル名を付ける必要があるようです。つまりやっぱり、ラベル名がひとつの鍵になっているようでもあります。

func make(prefix:String, _ middle:String..., suffix:String) -> String {
    
}

make("P", "A", "B", "C", suffix: "S")

ちなみに、当然ですけどさらに引数を続けて行くこともできます。

そこで、新たにラベル無しの引数をひとつ追加してみました。そうすると、たしかに分かりにくいと感じるかもしれないようになってきます。

func make(prefix:String, _ middle:String..., suffixes suffix:String, _ suffix2:String) -> String {
            
}
        
make("P", "A", "B", "C", suffixes: "S1", "S2")

これの最後の行を見て、どこが可変長引数でどこがそうではないか、たしかに分かりにくい印象を受けます。

ただし、あくまでも呼び出しに限れば、どこが可変長引数かどうかは知らなくても良くて、全体的に見通しの良いラベルをつけられるかが、ラベル省略の腕の見せ所になるとも言えそうです。

そして関数実装の方は、可変長引数かそうでないかで読みやすさが左右されることもなさそうに思えて、全体でみて、可変長引数が可読性を大きく損なう要因にはならないのかなと感じました。

つまり可変長引数を使うかよりは、ラベルをどこで省略するか、ラベル名を何にするか、そういった基本的なところが大事なように思えます。

Cocoa 勢力

そして、人によっては未来をも危惧する beta 6 での 3 つのメソッド名の変更についても、アイスブレイクでしっかり触れられました。

変更前 変更後
extend appendContentsOf
splice insertContentsOf
join joinWithSeparator

今まで、こういう長いメソッド名っていわば Objective-C のお家芸で、たとえば Swift の String でも stringByAddingPercentEscapesUsingEncoding みたいな長い名前のメソッドはあったりしましたけど、こういう名前のものってこれまでは Foundationextension で後付けしていたものだったんですよね。

それが今回、Swift 標準ライブラリにある既存の 3 つのメソッドがそういう風な名前になって、これから先の Swift 情勢に少し不安を感じるところだったりします。

メソッド名に影響しそうな仕様

そんなメソッド名の雲行きは以前からも若干窺えたりもしていて、Swift 2 の以前の Beta で、これまでは関数に限っては既定で引数ラベルは省略だったのが、最近になってメソッドと同じルールになったことが挙げられます。

func action(a:Int, b:Int, c:Int) {
    
}

たとえばこのような関数を定義したとき、かつては action(1, 2, 3) みたいに呼び出せましたが、今では action(1, b: 2, c: 3) みたいに呼び出す必要があります。


このときに思い出したいのが、若干違うイニシャライザのルールです。

final class MyObject : NSObject {

    init(a:Int, b:Int, c:Int) {
    
    }
}

イニシャライザに限っては、第1引数のラベル名も省略されず、たとえば MyObject(a: 1, b: 2, c: 3) のようにして実行する必要があります。

そしてこれが Objective-C にブリッジしたときには [[MyObject alloc] initWithA: 1 b: 2, c: 3] みたいな呼び出し方になります。自動的に "WithA" という Objective-C 文化らしい名前に変換されています。

ここまでで言えることとしては、とりあえず最初のラベル名には "withName" 的な役割を期待しているようにも思えます。


そして、次にメソッド名です。

final class MyObject : NSObject {

    init(a:Int, b:Int, c:Int) {
    
    }
    
    func action(a:Int, b:Int, c:Int) {
    
    }
}

こちらについては、たとえば全部のラベル指定が既定のままなら、次のようにして呼び出すことになります。

object.action(1, b: 2, c: 3)

ただ、これだとなんだかアンバランスです。

そこで、最初の引数にもラベル名をつけたくなるのですが、少し前の Swift までは次のようにして、定義で # を指定することで、ラベル名を明記させることができていました。

func action(#a:Int, b:Int, c:Int) {
    
}

object.action(a: 1, b: 2, c: 3)

ところがあるときこんな仕様に手が入り、既定で省略されるラベル名を # を使って省略禁止にすることができなくなりました。ラベルを省略させたくない場合は、引数名の前に外部ラベル名を明記する必要があります。

func action(a a:Int, b:Int, c:Int) {
    
}

自分はこの『同じ名前を2つ書かないといけない仕様』を見て、外部引数名は単純に『実装内部で使う変数名と表向きのラベル名とが直感的に一致しないときに調整するための手段として用意された』と捉えておくのが良さそうに感じました。

つまりこれまであった、引数ラベルを『必須にする』『省略する』『調整する』の3つの機能のうち『必須にする』という機能がなくなった、と捉えることもできそうです。

もう少し言うと『メソッドの第一引数のラベル名は原則省略』という主張が汲み取れるようにも思います。


そんなところを踏まえた上で、Objective-C 互換との兼ね合いです。

final class MyObject : NSObject {

    func action(a:Int, b:Int, c:Int) {
    
    }
}

先ほどのこの action メソッドを Objective-C にブリッジしたとき、次のようにして呼び出すことになります。

[object action:1 b:2 c:3];

ただしこのようなメソッドの場合、Objective-C では次のように "actionWithA:B:C:" みたいに呼び出すのが自然に思えます。

[object actionWithA:1 b:2 c:3];

こう呼び出せるようにブリッジしたい場合、Swift では次の2つの定義方法が考えられます。

final class MyObject : NSObject {

    // 第1引数にラベル名を指定する方法
    func action(a a:Int, b:Int, c:Int) {
    
    }

    // メソッド名を調整する方法
    func actionWithA(a:Int, b:Int, c:Int) {
    
    }
}

このどちらの方法を取っても、Objective-C にブリッジしたときに "actionWithA:B:C:" な呼び方ができるようになります。


ここでこれまでの話を振り返ると、引数名ラベル名の命名規則見直しから、自分には『メソッドの第1引数にラベル名を添えること』は推奨されていないという印象を受けました。

それが想像どおりだとすると、ここで取るべき方法は上記の『メソッド名を調整する方法』だけが残ることになります。すなわちメソッド名に "WithA" というのを添えることだけが、唯一の残された手段とも言えます。


つまり、今回の仕様変更と合わせて Swift は全体的に『メソッド名に With をつけたい』方向に進んでいる気がしなくもありません。

今後、自分が定義するメソッドは、そんな "With" な方向に足並みを揃えて行ったらいいのか、それとも引数ラベル名を第1引数から積極的に活用すべきか、しばらく悩むことになりそうです。

appendContentsOf メソッドと insertContentsOf メソッド

ところで、今回 Array に追加された2つのメソッドを改めて眺めて見てますけど、たしかに Swift 1 を学び始めてすぐの頃は extendsplice の意味が分からなくて戸惑った記憶があるので、それと比べて分かりやすい気もしなくはないところなのですけど、それでもやっぱりせめて、次のような定義に落ち着くことはできなかったのか、と思うところでもあったりします。

public mutating func append(newElement: Element)
public mutating func append(newElements: [Element])

public mutating func insert(newElement: Element, atIndex:Index)
public mutating func insert(newElements: [Element], atIndex:Index)

Swift ならこれでぜんぜん通用するので、この方が綺麗で分かりやすくて良いんじゃないかと思います。

ただし、これらそれぞれのメソッドは、Objective-C で見たときのシグニチャーが同じになります。たとえば insert ならどちらも insert:atIndex: になります。

Swift 純正のクラスであれば問題ないですが、これを NSObject から派生させたりするとビルドエラーが発生することになります。

Method 'insert' with Objective-C selector 'insert:atIndex:' conflicts with previous declaration with the same Objective-C selector.


そもそも、今回の appendContentsOfinsertContentsOf は Swift ネイティブな CollectionTypeプロトコル に定義されるものなので Objective-C 互換とは関係ない・・・と思ったんですけど、よくよく見ればプロトコルですね。

ということは、たとえば次のようなプロトコルを定義して、それを NSObject を継承したオブジェクトに準拠させたらどうなるでしょうか。

protocol MyProtocol {
    
    func append(newElement: Int)
    func append(newElements: [Int])
}

このようにして『Swift ネイティブなクラス』と『NSObject を継承した Objective-C 互換クラス』の両方にこのプロトコルを準拠させてみようとしたところ、Objective-C 互換クラスに限って、上で示したのと同じシグニチャーのビルドエラーが発生しました。

たしかに CollectionType もプロトコルなので Objective-C 互換クラスに搭載されることはあり得ます。


そう考えると、たとえば splice メソッドが分かりにくいという話になり、名前を insert にしようとしてみたら Objective-C 互換クラスで既存のメソッドと衝突したため、ラベル名で重複を避けようとしたが現在は第1引数にラベル名を添える機運ではない、迷った挙句 insertContentsOf にした、みたいな話の流れがあったりするかもしれません。

自分の勝手な憶測ですけど。

Join メソッド

同様に長い名前に変えられたものに joinWithSeparator があります。

こちらは、先ほど紹介した2つのメソッドとは少し様子が違っていて、これまでは String や大域に実装されていた join が Beta 6 からは SequenceType に実装されるように変わりました。

こちらもプロトコルに実装されるようになって、先ほどの仮説みたいに Objective-C 互換を視野に入れた名前付けになってるのかなと思いつつも、それでもやっぱり微妙な心地は拭えません。

func method(values:[String], separator:String) -> String {

	return values.joinWithSeparator(separator)
}

これまで見てきた Swift にしては、どうにも冗長な気がするんですよね。これがもし Foundation の拡張機能として用意されたものだったら違和感を感じないところなのですけれど。


そんなところに、この事態への懸念を上手に表現しているツイートが舞い込んできました。

mapWithFunction ・・・

今は幸いのところ map ですけれど、果たしてそんな未来が訪れたりとかするのでしょうか。

try?

そして Beta 6 で登場した try? のお話でした。

これは、前に追加された Error Handling の『エラーが発生した場合に戻り値を nil で得る』という新機能ですけれど、便利そうなような、ともするとエラーの重みを消し去ってしまいそうな、慎重に考えていく必要がありそうな機能です。

便利そうな面としては、これまでの do-catch でフローを制御しながらコードを書くのは何かと負担が大きいところ、新しい try? を使えばまるで真偽値でエラー判定できるかのような、軽量な扱いができるところです。

懸念するところとしては、この try? を通したときに、エラーだったときに投げられる ErrorType 情報は完全に破棄されてしまうところです。

二重オプショナルは問題ないかも?

ほかにもこれで、戻り値が得られるかエラーで戻り値が得られないか、という状況になるため、戻り値が Optional で包まれるのが厄介という話になりましたけど、その後の @cockscombさん の発表の中で『Optional を返しつつ throws をするパターンは考えられない』みたいな話になっていたことを考えると、それほど問題ないのかなとも思えました。

もう少し複雑な処理なら、戻り値が Optional かつ throws な場面もあるのかわかりませんけど、それがほとんどないと言えるとしたら try? で Optional に包まれたとしても、さして面倒事が増える場面というのもなさそうです。

Guard と相性が良いらしい

さてそんな、手軽に使えそうだけれどエラー情報が完全に落ちる try? をどんなときに効果的に使うことができるんだろう、そう思って質問をしてみたところ、@akio0911さん から『guard と合わせて使うと良い』という回答を頂きました。

func isValid() -> Bool {

	guard let status = try? validation() else {
	
		return false
	}
	
	return status == 200
}

たしかに、たとえばこんな有効性を判定するメソッドだったりしたときに、エラーが起こったら無効みたいな判断をしたいと思ったときに do-catch を使うよりずっと自然に書けそうです。


ちなみにこれを do-catch で書こうとすると、次のようになるのでしょうか。

func isValid() -> Bool {

	do {

		let status = try validation()
	
		return status == 200
	}
	catch {

		return false
	}
}

これくらいの規模ならどちらでもなんとかなりそうですけど、先に書いた guard を使う方法なら、最後まで読まなくても動きがすぐに掴めますし、後に書いた do-catch の方でも今回は結局エラー情報を捨てています。

自分の中では今のところは明らかに、最初に書いた guard を使った方法が良さそうに感じました。

エラー判定を if 文で行うということ

try? の特徴を整理すると、要は『エラーを do-catch ではなく if で処理する』というところに尽きそうです。

Error Handling が登場する前に使われていた NSErrorPointer 方式でいうなら、Error Handling が NSErrorPointer を使ったエラー処理であるのに対して、try? は戻り値を見て判定する方法と言って間違いなさそうです。


Error Handling が導入される以前であれば、ファイルの内容を読み込んで NSString に格納するには、NSErrorPointer を使って次のように行っていました。

var error:NSError? = nil

let string:NSString? = NSString(contentsOfFile: file, encoding: NSUTF8StringEncoding, error: &error)

これが Error Handling の導入によって、戻り値でエラー判定をする必要がなくなったため、非オプショナルな NSString になり、これまで引数で取得していたエラー情報が『投げられる』という形で通知されるようになっていました。

ただ、これまでもエラーを受け取る引数に nil を設定して、エラー判定は戻り値で行い、詳細なエラー情報は捨てることも行われていました。それと同等なのが、今回導入された try? と思って間違いなさそうです。

let string:NSString? = NSString(contentsOfFile: file, encoding: NSUTF8StringEncoding, error: nil)

この方法を取るときというのは、当たり前ですけどエラーの詳細情報が要らないときです。

そういう場面も今までプログラムを書いてる中で、あったような気がします。そんなエラー情報が要らないときまで do-catch を書かないと行けないのは辛いところですけど、try? があれば負担がずいぶん軽減されます。

代わりに、エラーを無視しやすくなるという問題もある訳ですけど、もしかしてそれを嫌って API を作る側がそもそも Error Handling を嫌って throws を使わないよりは、その API を使う側が try? で無視してくれた方が、全体的に見て健全なコードになるかもしれません。


さて、そんな感じで try? は『エラー処理を if で行うこと』と言えそうです。つまり、エラーがなければ何をする、そうでなければ何をする、といった制御の流れになるのでしょう。

そう思えば、先ほども述べた『戻り値が Optional で包まれる』というのも、なおさらほとんど問題にならないように思います。

if let value = try? readFromFile() {

	// 読めたらここで処理をする。
}
else {

	// 読めなかったら代わりの処理をする(エラー情報を使わずに)
}

取得できた値で何かをしたい訳ですから、エラー判定も含めると、通常は if let で受けるのが普通に思います。

ここでもし関数の戻り値が Optional だったとしても、if let で指定した変数には決して二重に包まれた Optional が設定されることもないので、そのまま普通に本来通りの値を使って行けそうです。

try? はエラーの重みを消すための道具?

そして、こう実際に書いてみて面白いなと思えたのは、エラーだった場合の処理です。

try? の特性として『エラー情報は破棄される』というのがあるわけですから、もしエラーが発生したときの else ブロックでできることって『代わりの値を設定する』くらいになると思います。

そうしたときに、最終的にはどちらも『正常系』と捉えることができると思います。

Error Handling は『正常系と異常系とを明確に分けて制御する』のが醍醐味だと思っているので、この動作は Error Handling らしくないと言えるようにも思います。ただ、正常系を if で分岐するやり方は、まさに戻り値が Optional な関数を処理するときとまったく同じ感覚です。

ここから言えることとして、もしかして try? は Error Handling の制御のしかたのひとつではなく『Error Handling の正常か異常か(エラーに着目)』を『Optional のあるかないか(存在に着目)』に変換する仕組みなのかもしれません。

そんな風に Error Handling と切り離して考えてみると、try? はけっこう使いやすい強力なツールなのかもしれません。

ほかにも if の else ブロックで『強制終了する』という手段もあるかもしれないですけど、そのときはエラー情報を通知するのが一般的でしょうし、もしそれが必要ないとするなら try! で十分なので、そういう場面で try? を使う必要はないでしょう。

戻り値が Void の場合も同様に扱える

そんな try? の話の中で『戻り値が Void な関数に使うと Void? が戻って来る』という話がありました。

それを聞いて『それは微妙・・・』と思いましたけど、上で述べたようなことを考慮したとき、それも大して問題にならないようにも思えてきました。


これまでの話で言えば try? を使うときというのは if 文を使ってエラーを判定するのが目的です。

そのため、エラーかどうかを判定できない戻り値では困るわけですけれど、幸い戻り値がない意味合いの Void であっても Void? が得られれば正しく判定できます。

if (try? action()) != nil {
		
}

実際にやってみると try? のひとまとまりを括弧でくくってから比較しないといけないため、コードに読みにくさを感じますけど、とりあえずちゃんと判定を行うことができます。

もう少し読みやすい書き方がないかと挑戦してみましたけど、次の書き方だと括弧が入り組まない代わりに慣れないと読めない、そんな読みにくいコードになってしまいました。

if case .Some = try? action() {

}

戻り値は注目すべきところではないので、あれこれ頑張って判定するのも、コードを読みにくくしているように思えます。

それよりも、戻り値がない場合はいっそ Optional への変換などせず、ふつうに do-catch で済ませてしまう手も普通にあるように思えました。

do {

	try action()

}
catch {

}

この方法をとったところで try? を使ったのと比べて、ネストの深さが変わることもなければ、処理のフローが変わることもなさそうです。

それよりむしろ try? ではエラーの重みが消えてしまう方が好ましくないかもしれません。戻り値が Void の関数はそもそも、戻り値がエラーで得られなければ別の値にするみたいな性質ではないですから、それが実行できた『正常系』と、その実行に失敗した『異常系』とで捉える方が賢明そうです。


そんなこんなで、自分の中では try? の登場によって Error Handling 積極活用に意識が傾き始めました。

Unicode シンポジウム

続いての話は、平川さん(@gooichiさん )の『Strings and Characters in Swift』へと移りました。

Strings and Characters in Swift from Goichi Hirakawa

Swift 2 になって String 周りも大幅に刷新されたんですよね。特に大きく感じるところは、これまでの『文字列のコレクション』という位置付けから、純粋に『文字列』へと変わり、それが表現する文字は View という形で参照することになったところでしょうか。

そんな文字列の基本的な話はほどほどに、どんどんと Unicode への闇へと話が展開して行きました。

結論としては『Swift の String は素晴らしい & Unicode の闇が辛い』みたいな話だったと思います。文字列の比較ひとつとっても、正準等価と互換等価と、さらにはそれがテーブルで管理されていたりするものだから、なかなか多くの闇を生み出すようです。

そんな超レアなお話に特にツイッターが超盛り上がりを見せておりました。

Unicode 規格に正確

Swift になって、文字列を構成する文字は Character型 で表現されて、文字コードが何だとかを意識しないで1文字を1つとして扱えるのが、嬉しく感じたところでした。

これまでは『内部では UTF-16 で持っている』とか、絵文字は2文字としてカウントされるとか、そういった具体的なところを意識する必要がなくて、単に『文字』という抽象的なイメージで考えることが魅力的と思って、それ以上は考えてなかったのですが、今回の発表を拝見して、それより先の世界を見せてもらえた心地がします。


Swift の文字列は『Unicode 規格に正確』と言えるんだそうです。

なるほど、そんな話の中で「ただ、Unicode 規格自体の出来はイマイチ…」みたいな流れになってましたけど、複雑なところに足を突っ込まない限りはかなり自然に文字を扱えるところを見ると、データとして Swift の文字列を扱う限りは負担をあまり感じないのは、きっと Unicode がよく出来ているということの表れとも言えるのでしょう。

表面的にとても綺麗を保ちつつ、中ではとっても頑張って混沌としているみたいなことって、たぶんきっとよくある話です。

4つのビュー

話の中で String型 が持つ 4 つのビューというお話がされてましたが、これが特に大事な特徴に思います。

とても理解しきれなくていくらか曖昧な表現になりますが、まとめると次のような感じです。

ビュー 概要
CharacterView 文字列を『文字の集まり』として捉える。ここでいう『文字』というのは、具体的には Unicode の『拡張書記素クラスタ』という『人が読める単一の文字』で表現されていて、それは1つ以上の『Unicode スカラ』で表現されているのだそう。
UnicodeScalarView 文字列を『UTF-32 で表現されたコードの集まり』として捉えるらしい。
UTF16View 文字列を『UTF-16 で表現されたコードの集まり』として捉える。NSString の内部表現と同じ。
UTF8View 文字列を『UTF-8 で表現されたコードの集まり』として捉える。

ビューという表現が面白いところに思っていて、文字列というひとつの型を何に着目するかによって見え方が変わってくるというところが、適切な扱いを選ぶ上での大事なところでもあり、普段はそういった具体的なところを無視して抽象的に捉えることで、扱いを容易にするポイントのように思います。

文字列の比較

文字列の比較についても、その具体的な方法として、単なるデータで見た等価判定の他にも『正準等価』と『互換等価』というものがあって、それらによって、同じように見える文字でも違うように判定されたり、明らかに違う文字でも同じ文字と判断されたり、いろんな闇を具体例に見させてもらいました。

そして、簡単にブリッジできる String と NSString とでもこの等価判定の仕組みが違ってくるとのことで、安易に変換して比較すると予期しない動作のもとにもなりかねない、そんなお話がとても印象に残りました。

foldr をとにかく早くする

続いて、吉田さん(@sonson_twitさん )の話です。

Swift 2 (& lldb) シンポジウム from Yuichi Yoshida

foldr といえば、Swift が登場して間もない頃から Haskell という関数型言語がその比較対象に挙げられ、その言語の中で登場する関数のひとつ、みたいです。

自分も最近 foldr の噂を耳にしていて、なんかスゴイものなんだなっていう認識は持っていました。

部分適用

そういえば余談ですけど、Swift って部分適用がしにくいように感じます。

部分適用といえば、いわゆるカリー化、引数リストを自由にひとつひとつ分解できる仕組みが重要なところに思いますけど、Swift の場合は意図的にそういう設計にしないといけなくて、そういう設計にすると引数リストをカンマ区切りで記すことができなくなって、何かと勝手が良くない印象です。

もっとも、クロージャーを簡単に書くことができるので、それを使えば部分適用もそう難しくはないところなので、積極的に使っていけば印象も変わってくるかもしれません。

let f = { max($0, 0) }

[1, -3, 5].map(f)

foldl

そしてまず、foldl を Swift で実装しようという話になって、いざ蓋を開いてみると、次のひとことで話が終了しました。

foldl = reduce

どうやら自分は、すでに foldl を知っていたみたいです。

今回は話の組み立てとして、Swift にはない foldr と対にして foldl を持ち出していることが明確なので良いし、はっきりと理解できて効果的ですけど、普段の Swift 言語の話の中で foldl とは言わずに reduce と言った方が混乱がなくてよさそうかなとも感じました。

たぶんきっと、次のようなコードを書いて使っていたら、たしかに混乱してしまいそうです。呼ぶだけと実際に定義して使うのは別でしょうし、そもそも実際に使うと確実に叱られそうな気がしますけれど。

extension SequenceType {

    public func foldl<T>(initial: T, @noescape combine: (T, Self.Generator.Element) throws -> T) rethrows -> T {
        
        return try self.reduce(initial, combine: combine)
    }
}

そしてさらに余談ですけど、個人的には foldl より reduce の方が読みやすくて好きかな、と思いました。ただ、どちらも慣れが必要そうですけど、意味的には foldl の方が動きを想像しやすいかもしれません。

foldr

そしていよいよ、今回の本題のひとつ foldr の実装です。

foldr = reverse().reduce

先ほどの foldl のノリで foldr が示されて、そこへ疑問が投げかけられるスライドの造りが、とても上手に感じました。

そしてこんなさりげないところに、議論を呼ぶ問題が潜んでいるというところになんだか惹かれます。

問題提起

スライドで挙げられた例を簡略化すると、次のようになると思います。

extension CollectionType {

    func foldr_reduce<T>(accm: T, f: (T, Generator.Element) throws -> T) rethrows -> T {
        
        return try self.reverse().reduce(accm, combine: f)
    }
}

これだと self.reverse のところで、単純な配列が返ってきてしまい、処理に時間がかかってしまうそうです。

解消策

これを次のようにして IndexRandomAccessIndex 誓約をつけることで、同じ self.reverse の結果が ReverseRandomAccessCollection で得られ、処理が高速化されるそうでした。

extension CollectionType where Index : RandomAccessIndexType {

    func foldr_reduce<T>(accm: T, f: (T, Generator.Element) throws -> T) rethrows -> T {
        
        return try self.reverse().reduce(accm, combine: f)
    }
}

想像ですけど、もっとも一般的な ForwardIndexType であれば、その値は前方に successor メソッドを使ってひとつずつ進めていくしか手がないため、その逆並びの配列を得ようとした時に、配列をひとつひとつ進めながら新しい配列の先頭に挿入していくみたいな作業が必要になるはずです。

これが BidirectionalIndexType になれば、先ほどの successor での前方移動に加えて predecessor による後方移動が可能になって、これによって新しい配列に詰めなおさなくても済むことが期待できそうです。

さらに RandomAccessIndexType を使えば、ひとつずつ進んだり戻ったりしなくてもいっきにまとめて前後することができますけど、配列を逆順にするという観点ではそこまでなくても大丈夫そうです。

BidirectionalIndexType を想定したい、かもしれない

話の中では RandomAccessIndexTypeReverseRandomAccessCollection が登場しましたが、もうひとつ前の BidirectionalIndexType を見てみると、それを想定した reverse の結果として ReverseCollection というものもありました。

そして、これを想定した次の定義も Swift 標準ライブラリにあったので、このレベルについても最適化が考慮された仕組みになっていそうです。

extension CollectionType where Index : BidirectionalIndexType {

    public func reverse() -> ReverseCollection<Self>
}

RandomAccessIndexTypeBidirectionalIndexType を継承しているので、少なくとも BidirectionalIndexType を想定しておけば、両方での最適化が期待できる、かもしれません。


もっとも、Swift の標準ライブラリで両方を定義しているところを見ると、それぞれで違う最適化が図られている可能性もあるため、両方を備えることも効果があるかもしれません。

ただ、もしかすると単に『元の配列がランダムアクセスできたのだから、逆順にした配列もランダムアクセスできて当然』という考えで実装されているのだとしたら、今回の foldr_reduce みたいな一時利用の速度面には大きく影響しないとも言えそうなので、最低限 BidirectionalIndexType 版を実装した方が、効果が高い可能性もありそうでした。

プロトコル拡張は広い視野が必要そう

いずれにしても reverse を使うメソッドを、ついつい単なる CollectionType に対して、プロトコル拡張で実装したくなりますけれど、ちゃんとインデックスまで考慮しないといけないということ、とても勉強になりました。

型に対しての extension であれば、そのあたりは型が準拠するプロトコルに応じて臨機応変にやってくれていたはずですけど、プロトコル拡張の場合はこういったシビアなところにも気を配って設計しないといけないとか、これはなかなか大変そうです。

as キャスト

ところでそんなお話の中で、reverse で得た結果を ReverseRandomAccessCollection でキャストすれば良いのではないかという意見が出ました。

それについて、自分は ForwardIndexType を想定した reverse が返す ArrayRandomAccessIndex を想定した reverse が返す ReverseRandomAccessCollection は別物なので、キャストすることはできなさそうに感じたのですが、いろいろ混乱してしまって、はっきりとした根拠を見つけられないでいました。


そんな中、それについてのひとつ例が、ツイッターに挙げられました。

つまり、こういうコードです。

["1", "2", "3"].reverse() as ReverseRandomAccessCollection<[String]>

これについて、なぜキャストできるのか、そしてなぜ as! ではなく as でキャストできているのか、とても興味深く思えたのですけど、これを突き詰めていく中で頭が整理されて、おかげさまでいろいろと見えてくるところがありました。

as でキャストしているということ

まず、キャストを as でしているという点で、これはキャストというよりは型を明記しているということになりそうです。

ErrorType と NSError や、String と NSString みたいな、as で型が大きく変わることもあったりしますが、ArrayReverseRandomAccessCollection とが as で変換されるとするには、納得する前に少しばかり疑惑の目を向けたくなる気持ちがあります。

型を明示している

そう考えると、先ほどの as は型を明記しているという前提で、確認してみたくなります。

そうしたときに、先ほどのコードで reverse を入力したときの補完候補ではたしかに ReverseCollection で結果が得られるかもしれない気がするものの、実際のコード定義を J でたどってみると、結果を得る時点で既に目的の ReverseRandomAccessCollection が得られることがわかります。

つまり、本来の戻り値の型をそのまま as で明示的に記載していることになるので、ビルドが通って普通に正しい答えが出た、という動きになる様子です。

Array の reverse を直接呼び出したとき

この ReverseRandomAccessCollection を返す reverse は、発表の中でもあった CollectionTypewhere Index : RandomAccessIndexType で縛ったときの実装です。

今回の話はそもそも、これに縛った reverse が呼び出されないことに起因した速度低下のお話なので、これが何も配慮なくして呼び出せているところが不思議なようにも思えますけど、これはゆっくり考えてみると見えてきます。


このコードでは、呼び出し元の値が ["1", "2", "3"] で指定されています。

これはすなわち Array<Int> 型のインスタンスです。そして Array のインデックスは、標準ライブラリの中で Int であると定義されています。さらに IntRandomAccessIndex に準拠するよう設計されています。

すなわち Array から直接呼び出す reverse は、ReverseRandomAccessCollection を返す方の reverse ということになります。

問題を再現するには

今回の話の発端を再現するには、いったん型を純粋な CollectionType に縛る必要があります。

func reverse<T:CollectionType>(array:T) -> Array<T.Generator.Element> {

    return array.reverse()
}

このときは、インデックスは ForwardIndexType であるものとして判断するしか術はないため、戻り値は当然のように Array になります。

Swift のジェネリックは静的に型を決める方向で働くはずなため、この時点で、実際に引数に渡される値の型とは関係なく、ForwardIndexType を前提とした方の reverse が呼び出されることが確定します。


こうしたときに、次のように、先ほどの Array<Int> のインスタンスを渡してみると、結果は ReverseRandomAccessCollection ではなく Array で得られます。

// この方法だとキャストに失敗する
let r = reverse(["1", "2", "3"]) as ReverseRandomAccessCollection<[String]>

この方法だと、その後に as でキャストを記載しても失敗します。代わりに as! を使って強制的にキャストをしても失敗しますし、as? を使ってキャストをしても nil しか得られません。

再現した問題の解消策

今回の場合は次のようにして、インデックスの種類をジェネリックで RandomAccessIndex に縛り、戻り値の型を ReverseRandomAccessCollection にすることで解決できます。

func reverse<T:CollectionType where T.Index : RandomAccessIndexType>(array:T) -> ReverseRandomAccessCollection<T> {

    return array.reverse()
}

余談

余談ですけど、インデックスの種類をジェネリックで RandomAccessIndex に縛っても、戻り値の型を Array<T.Generator.Element> にしたままだと、相変わらず ForwardIndexType を想定した方の reverse が呼び出されます。

func reverse<T:CollectionType where T.Index : RandomAccessIndexType>(array:T) -> Array<T.Generator.Element> {

    return array.reverse()
}

これは決して ReverseRandomAccessCollection が戻り値として明記した型の Array<T.Generator.Element> にキャストされたとかではなくて、戻り値が Array<T.Generator.Element> として実装されている方の reverse が呼び出されたことに因るためです。

つまり、型推論によって戻り値が Array<T.Generator.Element> であるということが推測され、それに適切な値を返す ForwardIndexType を想定した方の reverse が選ばれたということになります。

戻り値の型によるオーバーロード

そうでした、さきほど戻り値のキャストの話がでてきたところで、そういえば議論の中でも話題に挙がった、戻り値の型でオーバーロードしたときのリズムの話をしておきたくなりました。


戻り値の型でオーバーロードするというのは、単純なところでは次のような場面です。

func getValue() -> Int {
    
    return 3
}

func getValue() -> Double {
    
    return 3.14
}

Swift ではこのように、同じ関数名で引数リストが同じでも、戻り値の型が違えば、これらは別の関数として存在させることができます。


ただ、これをいつものように使おうとすると、次のようにエラーになります。

let value = getValue()

Ambiguous use of 'getValue()'

これは、2つあるうちのどちらを実行したら良いかが呼び出しのコードだけではわからないためにエラーになっています。

変数に型を指定して関数を切り替える方法

これを解消するためには、次のように変数の型を明記することで、オーバーロードされているうちのどちらを呼び出せば良いかが明らかになって、これでちゃんと使い分けることができるようになりました。

let value:Int = getValue()

ただ、こういう書き方は、いざ getValue を使おうとしたときになって、どちらの型を返す方を使うかを、変数定義のところまで戻って書き足さないといけないため『コードを書くリズムが狂う』と避けられがちです。

戻り値をキャストで切り替える方法

そういうリズムの問題であれば、次のように書くことで、手戻りすることなく関数を使い分けられます。

let value = getValue() as Double

この書き方なら、もし getValue を書いているときに戻り値の型でオーバーロードされていることに気づいても、そのままの流れで続けて as を書いて型を書けば、これで目的の関数を呼び出すことが可能です。

この書き方、自分はけっこう好んで使ってます。

戻り値のオーバーロードが活きる場面

そんな戻り値のオーバーロードは、型推論がされるところで活きてきます。

たとえば、引数に Double 型を取る abs関数 があったときに、次のようにするだけで、戻り値の型が Double の方が採用されます。

let value = abs(getValue())

他にもたとえば、次のようなコードを書くと、足す値の種類に応じて自動的に呼び出す方が選択されたりもします。

// 戻り値が Int の方が呼ばれる
let a = getValue() + 1

// 戻り値が Double の方が呼ばれる
let b = getValue() + 1.5

戻り値の型のオーバーロードを作ったときに、全ての場面で効果的に働くとは限らないかもしれませんけど、たとえばどんな型としても扱えるバリアント型を作るときとか、何かの値を別の型の値にキャストする役目の関数を作るときに、型に合わせて関数名を覚えたり探したりしなくても良いのは嬉しいように思います。

Value or Reference

そんな @sonson_twitさん の話の後半で、Value type programming についての投げかけもありました。

自分も原則 struct を使うことを前提に Swift を学習してきましたけど、あまり志向的なものとしては捉えていなかったせいか、話の中の『ここは Value type で行くべきか』とか『Reference Type で行くべきか』みたいに描かれる話の課題点を上手く掴みきれませんでした。

ちなみに自分は今のところ『値型は構造体で作り、値型の制御をクラスで作る』みたいな捉え方でいて、適材適所で切り替えるみたいな感覚にはまだ至っていません。


そんな状況なので適切な検討はまだできなそうに思いますけど、良い機会なので発表資料を眺めながら、思い当たるところをざくざくと綴ってみたいと思います。

Value type

まず、Value type にみる特徴として、発表の中では次の点が挙げられていました。

権限と役割が明確

権限と役割が明確という点については、値型ということなので役割は明確と自分も感じます。

そんな役割については、自分はクラスを「値を制御するもの」みたいに明確に捉えて考えてみているところなので、自分の中ではリファレンスタイプ、多分ここでは「クラス」のことをそう呼んでいるのではないかと思うのですけど、それについても同じように『役割が明確』に、今のところの自分の中ではなっています。

速い・・・?

速いというのは、どういうところからくるものなのだろう、と、復習していて思いました。

マルチスレッドでの処理に向いているから、全体的に見て処理が完了するのが速いという意味なのでしょうか。それとも Swift の基本的な値型は『必要時にコピー』が実現されているから、そうではない値型と比べて、取り回し時の不要なコピーが実施されないから速いということなのでしょうか。

それとも、もしかして値型のコピーされるという性格によって、リファレンス型よりも速くなることなのかとも考えたのですけど、簡単に考える限りは、そういうことはなさそうにも思います。

あるとするなら『マルチスレッド時にロックをしなくて済む』ということもあるのかなと思いましたけど、Swift の値型って『必要時にコピー』が災いして、ちゃんとロックしてあげないと同時アクセスで不正終了みたいなことも普通にあるように見えるので、マルチスレッドでロックをせずに済ますためには、リファレンス型で明示コピーをするのと同じくらいの負担のように思います。

それか『参照カウンタをその都度操作しないで済む』みたいなのもあるかもしれませんけど、必要時コピーを実現するには「共有されているか」を管理する必要があるでしょうから、内部的には参照カウンタみたいな何かで共有状況を管理していると考えられます。

それかもっと、別のところで「速さ」に影響する何かがあるのかもしれません。

ポインタに慣れきっていると辛い

これについては、もしかすると自分はあまり評価できる段階にないかもしれません。

自分が初めて使った高級言語は C++ で、クラスも(たしか)値型的に扱えたような気がするのが影響してなのかどうか、原則参照型な Objective-C を数年やり込んできましたけど、そういえばあまり「参照型」を活用するという意識でコードを書いてこなかったように思います。

それよりも、初めて作った iOS アプリで参照渡し+マルチスレッドでの同時処理による動作不良に悩まされ、むしろイミュータブルクラスと NSCopying 周りの理解を深める方向で学習を進めていたように思います。

そんなせいか、Swift で登場した構造体は、それを徹底かつ負担なく導入するための仕組みとして、今のところとても好意的に受け止めている次第です。

コードがわかりやすい?とは思えない

コードのわかりやすさというと、どうなのでしょう。

とりあえず、代入文を書いた時には左辺と右辺とは独立して存在する、というルールが馴染んでくれば、参照型のように copy を実行したかどうかで、両方の値が連動するかが変わるのと比べれば、わかりやすいようには思います。

また、クラス型を使った時には、連動して変わるというか、そこで管理されているデータがいつ更新されるかわからない、みたいな意味がコードに込められて、そこから値を取り出す時にはコピーをしたり、あるとき突然 nil になっていたらどうするとか、そういった周囲の影響を想定したコードが省ける点においては読みやすい、わかりやすい、そんなコードになるかもしれないようにも思います。

ただし『そう思う』というレベルであって、クラス型を構造体型に書き換えたら分かりやすくなった、みたいな明確な経験は持っていないので、本当のところはわかりません。

そうそう、これも想像ですけど、クラスみたいに継承して機能を追加していくことがないのも、わかりやすさに貢献する可能性のひとつのように感じるかもと思いましたが、これは単に Swift の値型がたまたま構造体で継承機能がなかっただけのお話ですね。

Reference type

そして、Reference type、たぶん「クラス」のことだと思うのですが、それにみる特徴として、発表の中では次の点が挙げられていました。

C 型オールドタイプにはわかりやすい

そういえば自分は C++ くらいからしか C 系の経験がないんですど、構造体を取り回す時って、ポインターで扱うことが多いですよね。

クラスの場合に限る話なのか判らないですけど、その時は値渡しと参照渡しとを使い分けるみたいなこともありましたけど、大抵は const MyClass& みたいに扱うのが都合良いことが多かった気がします。

この辺りの参照渡しによるメリットを、Swift では値の『必要時コピー』で暗黙的に捻出しているのかもしれないな、とも思ったりしました。


たしかにそう言われてみると、プログラムの動きをよくわかっている人には Reference type は分かりやすく感じるかもしれません。

そもそもは「参照渡し」か「値渡し」かというところではありますけど、Swift の暗黙コピーや必要時コピー、原則として『これは今、参照で渡すべきか』みたいな高度な判断をプログラマーに迫らない姿勢は、もしかして車でいうところの「MT 車」と「AT 車」くらいの差に近いのかもしれないなとか漠然と思ってみたりしました。

権限と役割がめちゃくちゃ & たまにデータがめちゃくちゃになる

たぶんそうなるのかもしれません。

権限と役割は、構造体なら varlet や、原則コピーという性質で言語による統制がされるのに対して、クラスは言語による介入がたぶんないため、プログラマーの自己責任に委ねられることになりそうです。

でもきっと、この特徴は先ほどの Value type の特徴と対になる感じで挙げられていて、おそらく @sonson_twitさん さんにとってはあまり混乱するポイントではないような気がしなくもありません。

たまに悲劇は起こるでしょうけど。

リストを更新するときの懸念

ここが特に、自分が理解せずに過ぎたポイントでもあったりします。

スライドでは ListViewController がコールバックでリストを受け取り、そして DetailViewController でリストのひとつを受け取り詳細表示・編集して書き戻すという流れでの Value type の在り方についての話だったと認識しています。

そしてそのうちのひとつを詳細画面に渡して書き換えた後、戻るときにその内容を書き換えるところで Value type は辛いから Reference type にした方が良いとか、差分を検出するのが妥当かもとか、Flux を導入すると良いとか、話がとても膨らんでいました。

自分の中での想像は・・・

コールバックでリストを得たときは、リストをそっくり書き換えて良いだろうと、まず思います。

そして、コールバックでリストを更新しているとすると、そのリストはきっと varListViewController が持っているのだろうと想像できます。

そのうちのひとつが DetailViewController に値で渡されたとするなら、何番目の値が渡されたかはわかるはずで、編集が終わったときには ListViewController で、新しい値を返してもらい、その項だけをリプレイスする、みたいな方法だと支障があったりするものでしょうか。


ここまで考えていて、もしかしてその、ひとつの値が大きい場合の話なのかもしれないとも思えてきました。

もしもその扱うデータが、たとえば電子カルテみたいな大きなデータだったとすると、確かに全部を渡して全部をもらって、全部を差し替えるというのは、ともすると Value type は大きく支障をきたすところなのかもしれません。

そういうときは、どうするのがいいのでしょうね。

たとえばひとつ、カルテマネージャー(ドライバー?)みたいなクラスを作って、それがカルテデータを構造体で内包して、カルテマネージャーを通してカルテを更新する、みたいな作りをするのが良いのかな、と、とりあえず安直にですけど考えてみました。

やっていることは「カルテをクラスで作る」ことと同じだと思うので、それを Reference type と普通は呼ぶのかもしれません。

try と rethrows 再来

今回の話題の中で自分がいちばん面白いと感じたテーマが、加藤さん(@cockscombさん )の 『Error Handling in Practice』でした。

どこがそんなに良かったかというと、とってもシンプルに「どっちがいい?」と投げかけているところが、ものすごくシンポジウム形式にマッチすると感じたところです。

でもそれだけならそこまで感動しなかったかもしれないですけど、その投げかける題材が、誰もがひと目で理解できるものでありながら、大いに意見が分かれそうな良いポイントを突いていて、しかもその議論が無意味に終わらないという、そんな題材選びの目利き感さえ味わえるような発表でした。

parseInt と parseJSON

Optional を使って失敗を通知するのと、Error Handling を使って失敗を通知するのと、どちらが良いか。

そんな投げかけを、parseInt と parseJSON という2つのメソッドに対して投げかけるという、とても単純そうでありながら、そこにはかなりの議論の余地を生む仕掛けがありました。

func parseInt(string: String) -> Int?
func parseInt(string: String) throws -> Int
func parseInt(string: String) throws -> Int?

func parseJSON(string: String) -> JSONObject?
func parseJSON(string: String) throws -> JSONObject
func parseJSON(string: String) throws -> JSONObject?

自分が最初に感じたこと

これを見て、次のそれぞれが最も適切かなと、議論を始めた当初は感じました。

func parseInt(string: String) -> Int?
func parseJSON(string: String) throws -> JSONObject

理由としては、まず大きいところとして parseInt は、文字列を数値に解釈するということの意味を『変換できないことも普通にある』と暗に捉えているところが大きいように思えました。

それに対して parseJSON で JSON を解析するときは『常識的に JSON 形式のデータを受け取っていることが前提で、原則として JSONObject に変換できるべき』と暗に捉えていたように思えます。


そのため、parseInt には『エラー』という重みを添えない Optional を、parseJSON には『エラー』という重みを添える Error Handling を、選んだということになると思います。

他にも添える理由はあって、 parseInt の場合は変換できない外的要因って『文字列が数字に則していない』といったくらいで、parseJSON みたいに様々な要因を想定しなくても、変換できなかった理由を安易に想定できるところにわざわざ Error Handling ならではの詳細にエラーを伝える機構は重すぎるのではないか、そんな感覚もありました。

そんな話を議論の中で話させて頂いたのですが、JSON についてはほとんど皆が同意なところ、parseInt については大きく意見が分かれることになりました。

揺らぎ始める自分の意見

そうしてみんなであれこれ意見交換をしていく中で、自分の意見を見直したくなる2つの意見が挙げられました。

そのひとつは『自分は parseInt に、数字へと確実に変換することを期待する。文字列の変換理由にもいろいろあって、単純に数字以外の文字が混入していることもあれば、オーバーフローで数字に表現できない場合もある』というお話でした。

そう言われれば、そういう視点で見れば変換できないことはエラーです。ただ、そうは聞いてもそこまでの役目を期待するものなのか考えあぐねるところでしたが、それはさておき、確かに数字の変換に失敗する理由って、探していけばいくつかあるように思えてきました。


そしてさらに、次の言葉が考えを大きく揺るがしてくれます。

たしかに、根本的な Error handling の醍醐味を、忘れていたような心地がしました。

try?

そんな Optional にすべきか、Error Handling にすべきかの議論がひとまず、良い具合に禍根を残して過ぎ去ったあとで、改めて思い起こしてみると、最近登場した try? の特徴をちゃんと意識しないまま、意見を展開していたかもしれないことに気がつきました。

議論の中でも parseInt を Optional で返す派の意見の中で、自分も含めて『Error Handling は重すぎるので Optional が妥当』という意見が挙がりましたけど、そのときに加えて『try? でもできるんですけどね』というセリフが付けられることが多かったように思います。


そう、この記事の上でも触れましたけど try? で Error Handling の重みは消せるんですよね。なんかその特徴を自分は、知っているつもりになっていながら、実感できていなかったように思えてきました。

それも踏まえて再度検討してみると、たしかに機能を提供する側の姿勢としては、Error Handling を積極的に導入していって、何も問題ないようにさえ思えてきました。

それこそ機能を提供する側が、エラーを Optional で返したとしたら、使う側は Error Handling を使えなくなってしまいます。もちろん nil だったら throw するコードを自前で用意することはできるでしょうけど、それって Error Handling を活かしているとは言えないので。


そんなこんなで、議論が終わった今ですけれど、自分は『parseInt を Error Handling で実装するべき』という意見に、今のところ気持ちは転向しました。

try と rethrows を活かす例

そう思うきっかけになったもうひとつの理由に rethrows があります。

最初のアイスブレイクで登場した『map などの主要関数が rethrows に対応した』という話題に対して「念願の rethrows 対応で嬉しい」という意見と「具体的に何が嬉しいの?」という議論が展開されました。


それに対して、なかなか良い例が思いつかなかったのですけど、今回の Error Handling と、この後のパターンマッチの話を受けて、ひとつ興味深い話が挙がってきました。

この話のそもそもは『parseInt みたいなシンプルなものも Error Handling にしてしまうと、たくさんのものをいっきに変換して一括で計算するみたいなことが難しくなる』という意見のもと、Optional で値を返してそれを上手に裁く一例として挙げられたものと思います。

じゃあ、これを Error Handling な parseInt で実現しようとしたとするとどんなコードになるのだろうと考えてみたとき、try?rethrows 対応 reduce を使うと、思ったよりもシンプルに実現できる様子でした。

全部変換できたら足し合わせる。ひとつでも失敗したら nil にする。

つまり、お題としては『いくつかの文字列について parseInt での変換を試み、全てが変換できた場合はそれらを足しあわせた結果を得る、ひとつでも変換に失敗したら nil を結果として得る』というコードを書くことになります。


そこで、まずは Error Handling 対応版の parseIntString に追加します。

extension String {
	
	func parseInt() throws -> Int {
		
		guard let value = Int(self) else {
			
			throw ParseError.InvalidValue
		}
		
		return value
	}
}

こうしたときに、お題のコードは次のように書けました。

let result:Int? = try? ["1", "2", "x", "4", "5"]
	.reduce(0) { try $0 + $1.parseInt() }

お題のコードを解説

ぱっと見て少しややこしいので、もう少し単純になるように整理してみます。

let result:Int?
let values:[String] = ["1", "2", "x", "4", "5"]

result = try? values.reduce(0) { try $0 + $1.parseInt() }

try が2つも出てくるところに慣れない心地もしますけど、末尾クロージャーの中で指定した try は、その中で使っている parseInt で発生するかもしれないエラーを『クロージャーの中では解決せずに、呼び出し元の判断に委ねる』という意味合いです。

これができるようになったことが、reducerethrows に対応したことがもたらすメリットのひとつです。これによって、もしクロージャーの内部でエラーが起きても、それをそもそもの reduce の呼び出し元に一任できるようになりました。


そうして、もうひとつの try? の出番です。

ひとつでも変換に失敗したら nil にしたいのが今回の要望な訳ですけど、先ほどの rethrows 対応 reduce のおかげで、処理中にひとつでもエラーが発生すれば、reduce でエラーが発生したものとして、処理を中断することができます。

そして処理がエラーで中断されたとき、エラーの種類はどうでもいいので結果を nil に差し替えたい、それならまさに try? がぴったりです。


こんな風に、今回のお題は、reduce という for ... in とは違って途中 break などで抜け出す制御のできない世界の中で、Error Handling による『正常系と異常系』というフローが変わる特性が活きました。

しかもそれをちゃんと活かすことができたのは、新たに reducerethrows 対応したという仕様変更のおかげとも言えます。

まだまだ研究の余地あり

こんなコードを書いてみたときに、まだまだ Error Handling を研究する余地はありそうだなと思うと同時に、これなら parseInt が Error Handling を採用するのは大いにアリなのではないか、そんな風に思えました。

そしてこれが、自分が最初に抱いた『parseInt では Optional を使うべきだろう』という意見が『それがエラーの意味を少しでも意味するなら Error Handling で表現しよう』という意見に転換する、大きな理由のひとつにもなりました。

Error Handling の改良に期待

ただし、まだ素直に全て Error Handling に移行すべきとは思えない点があって、議論の中でも挙がりましたが、それは現状の Error Handling は Swift の取り柄とも言える型安全のメリットが実質得られないというところです。

投げられるエラーは確かに ErrorType で自由に表現できて維持されるので、それ自体は良いのですけど、肝心なその投げられたエラーが何なのか、事実上、把握していないと適切な型で拾えません。

結局のところ、ドキュメントで発生し得るエラーの種類を明記して、呼び出した人はそれを見ながら適切なエラーをキャッチするという、まるで C 言語の errno 時代へ逆戻りしたような有様なので、これを改善しないことには、積極的に Error Handling を導入することは難しいかなと思えるところです。

もしかして伏線?

しかし、これについてはもしかして Xcode 7 beta 5 で構造体やクラスも ErrorType に準拠できるようになったことが何か伏線になっていたりとかするものでしょうか。

それまでは結局、網羅性が売りの列挙型を ErrorType に準拠させたところでその価値を全く生かせてなかったですけど、ErrorType の表現が構造体やクラスへも広がったことで、これを活かして何か上手なエラーの取り回し方を発見できたりしないかなと思いつつも、今のところは特には何も閃いていないところです。

Either を標準導入するには『まだ早い』かもしれない

そんな Error Handling 周りと対をなすように、成功か失敗かという状況を値付き列挙型で表現する Either 型が登場しますけど、もしかしてそれを標準で導入するのはまだ早いのかもしれません。

というのは単純に、まだ Error Handling についてちゃんと理解ができていない、という理由なのですけれど、エラーの通知に Either 型を導入するのはもう少し Error Handling を積極的に使って、その世界観を把握してからでもバチは当たらないかな、と思うようになってきました。

そう思わせてくれたのは、間違いなく try? の登場です。


もちろん Either 型にも、今でも適切と思える場面があって、それは前回の Swift 2 シンポジウムで稲見さん(@inamiyさん )が挙げられていた、非同期処理の場合です。

この場合は、エラーの重みがどうとか以前に、非同期処理の結果を返す手段として、成功か失敗かの情報も上手に含められる Either 型が、今後もずっと適切なように思えています。

Patterns in Swift

Error Handling の話題の次は、石川さん(@_ishkawaさん )の『パターンマッチ』のお話です。

このスライドはパターンマッチの入門にとても適してるように感じます。

主題をシンプルに『文法がよくわからん』に設定して、具体例を幾つか挙げて微妙な書き方の違いや動きの違いを紹介し、それを標準ドキュメントと照らして丁寧に紐解いて行くという造り。

そうして分かった気になったところで、改めて過去にわかりにくいと感じたところを取り上げて、おさらい的に読んで見せるという、教育的なスライドに仕上がってました。

expression pattern

最後に switch でのパターンマッチの例を挙げて、これが広く規定されたパターンマッチのひとつであることを印象付ける造りもいいなって思いました。

Swift の switch がパターンマッチで実現されていることは Apple の出している "The Swift Programming Language" を眺めていたときに知って、それが ~= で実現されているところまでは自分も知っていたんですけど、それを iffor のパターンマッチと関連付けて考えることを忘れていたので、自分の中では盲点でした。

switch で使われる ~= 演算子は expression pattern に分類されていたんですね。それを知って、パターンマッチに対する視野が広がる心地がしました。

治安の悪いコード

ところで、どういう流れで登場した言葉が忘れてしまったんですけど、話の中で出てきた『治安の悪いコード』という言葉がとっても気に入りました。

無闇に強制アンラップを使うと治安の悪いコードになる、そんな風に使っていたと思うんですけど。


自分の中で、石川さんといえば何故だか『強制アンラップを良しとしない人』という印象がとても心に残っています。そのきっかけは、たしか !を避けたいときは@noreturnが使えるかもしれない を拝見したことだったように思います。

ちなみに自分は「強制アンラップを好んで使う人」とも言える気がしますけど、それでも石川さんの主張と大きく違っていないような気がする、そんな風に思えていました。

強制アンラップは伝染する?

そんな折、今回のお話の中で先ほど挙げた『無闇に強制アンラップを使うと治安の悪いコードになる』という言葉が耳に舞い込んできます。

そして、その後に続けて『いや、いいんですけどね』という言葉が聞こえてきました。その理由が、それを見た誰かが真似て強制アンラップを使い出すと収拾がつかなくなる、すなわち治安が悪くなる、そんな感じで話を展開されていたと思うのですけど、それを聞いてとっても納得できました。


たしかに、チームでプログラミングをするときを思うと、強制アンラップをしないという姿勢を見せることに大きな意味があるかもしれません。自分は普段、ひとりでプログラミングをしているので、そういう悪影響というものを考えたことがなかったんですけど、そういう目で見ると収拾つかなくなる絵も想像できます。

本当に有り得ない場面で、もし有り得たらそれ以上は進めたくない場面で、そういうことを考えながら強制アンラップを採り入れるのは、可読性的にも問題を切り分けるためにも、効果的に働くよう自分はに思います。ただ、その使う場面をしっかり見極めないまま使えば、それはあっという間に、安易に百害あって一利ない道具に転落するのは間違いないということは察しがつきます。

そういう観点で見て、それを見る目が複数あるとき、強制アンラップを極力避けるという方向は、とても良い主義に感じられました。

今後も自分は積極的に

そして自分は、これからも強制アンラップを積極的に使っていきたいと思います。

それは、絶対に nil が有り得ないところで安直に Optional を使ったり Optional Chaining であやふやにしたりしたくないというのもありますし、ギリギリのところを追求したいというのもありますし、強制アンラップの表現力の可能性を知りたいというのもあります。要は単に好奇心です。

そして今回の石川さんの話で得たものとして、もし今後、自分が誰かと Swift プログラムを書く機会に恵まれたときには、自分のコードで治安を悪くしないよう、節度を持って使っていけたらいいなと思いました。

まとめ

最後がとっても微妙な話になったかもしれない気がしなくもないですけれど、今回の振り返りはひとまずここでおしまいです。

今回もたっぷり勉強でしたし、さすが2回目の開催とあって、発表者の方々も議論を見据えた題材を提供してくれたこと、それを聞く側も積極的にツイッターで反応してくれたこと、それらがあってとても良い会になったように思います。


深い議論というと少人数でなければ成し得ないと思うところもありますけれど、逆に多くの人が集まるほどに広い知識が得られる側面もあるはずで、この Swift 2 シンポジウムは後者の利点を上手に活かした会なように感じました。

やっぱり、スピーカー・パネリスト・オーディエンス、この3つが設定されていることが功を奏しているんだろうなって、前回に続いて同じくそう思いました。とっても楽しい会でした。

どうもありがとうございました!