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

swift の protocol の解決が Playground 上でうまくいかないっぽい話

えー。尊敬するiOSアプリケーションプログラマのひとりであるdictavさんがこういうものを上げておりました。

Logger for Swift

これを見て最初に思ったのが、「static var とかセオリーで考えたら「ねーよ」っていう感じだけど、この場合に限ってはうまく作用してておもしろいな」でした。

その次に思ったのが、「あっでも description に関しては var である必要なくない?」でした。というのも、この desciption は固定でいいはずだから。

しかし、この var description は、そもそも Printable プロトコル(ジャバで言う所のinterfaceみたいなもんや)が要求するするものであるので、そもそも「description は let でよくない?」というのは的外れな考えですね。

というわけで、Printable プロトコルについて調べてみましょう。

Printable プロトコルとは

Swift Standard Library Reference: Printable

ここに全部書いてある。要するにそのクラスのインスタンスを文字列化するときにどういう形で文字列化するかっていうのを var description というところに定義しておいてね、っていうプロトコルです。

試してみる

で、これをみて私はまず「enum が Printable プロトコル実装できるのおもしろいな〜」って思って、Xcode の Playground でこういうふうに書いてみたんですね。

http://i.gyazo.com/70dda2bb1299d383cae6dcf8ac961616.png

あっあれーーーーー!?println(Nyan.Nyaa.description)はちゃんと "にゃー" を返すのにprintln(Nyan.Nyaa)が"(Enum Value)"を返す!?!?!?!?!?

というのを見て「なにこれどういうこと」と困惑していたら、Twitterから「これアンドキュメンテドだけど、Xcodeでちゃんとプロジェクト作ってビルドするとprintln(Nyan.Nyaa)も"にゃー"だよ」という情報を得ました。そこで私は「あれっこれ(つまり enum の この挙動)って要するに「未定義」な振る舞いなんじゃないの」って思ってしまったんですね。疑問に思ったら一次情報に当たるのがプログラマのたしなみです。仕様上の定義を探す旅に出ましょう。

まずはレキシカルな仕様を調べる

まずは言語のLexicalな仕様を当たりましょう。これは要するに「そのプログラムを文字列的(形式言語的)に解釈したときにただしいプログラムとして受理できるかどうか」を調べることにあたります。

The Swift Programming Language: Lexical Structure

Lexicalな仕様がきちんと公開されていました。

この中で関係ありそうなのは The Swift Programming Language: Declarationsenum-declaration の部分で、抜粋すると

http://i.gyazo.com/92923d52b9aeaadfe2b0137341031b64.png

とあります。enum-declaration の部分をみると、enumの定義が受理する文字列のルールはふたつあるようです。

今の関心は

enum Nyan : Printable {
    case Nyaa, Mew
    var description: String {
        switch self {
        case .Nyaa: return "にゃー"
        case .Mew: return "みゅー"
        }
    }
}

なので、多分 attributes と access_level-modifier は(optionalだし)関係ないだろうなとあたりをつけ、union-style-enum­ が受理する文字列や ­raw-value-style-enum­ が受理する文字列をさらに見ていきます。swift をちょっと知ってると、上に見た文法はraw-value-styleではないとわかるので、union-value-style-enumにあたりをつけてみていきましょう。   まず、union-value-style-enum は 以下のルールを受理します。

enum(固定文字列) ­enum-name­ generic-parameter-clause(­opt) ­type-inheritance-clause(­opt) ­{ ­union-style-enum-members­(opt)­}

ここから、Nyanの定義のうち "Nyan" が "enum-name" であり ":Printable" が ­type-inheritance-clause であることが推測できます。 深追いはしませんが enum-name は Nyan を受理するし type-inheritance-clause は " : Printable" を受理することが同様に深追いしていくとわかります。

さて、問題は ­union-style-enum-members­(opt)­ です。こいつが

    case Nyaa, Mew
    var description: String {
        switch self {
        case .Nyaa: return "にゃー"
        case .Mew: return "みゅー"
        }
    }

を受理するかどうがが今一番の関心ですが、union-style-enum-membersunion-style-enum-member union-style-enum-members(opt)となっており、union-style-enum-member複数集まったものだと見て取れます。で、union-style-enum-member は declaration | union-style-enum-case-clause­ とあり、今回の例で言えば case Nyaa, Mewunion-style-enum-case-clause­にあたり、var destription 以下がdeclaration­ に当たることが推測できます。で、実際にこいつらをさらに追ってみると、上記のようなNyan enumの定義がレキシカルには受理されるべきものであるということが確認できます(実際に追ってみてください)。

さて、これでこれが「レキシカルには受理されるべきものだ」ということが確認できました。

次は意味を調べる

では、次は enum における computed property (var varname: Type { codeblock } みたいなやつはcomputed property と呼ばれる)が言語仕様上どういう意味を持つのかってことを見ていく段階ですね。探すと、出て来ました。

The Swift Programming Language: Enumerations

Enumerations in Swift are first-class types in their own right. They adopt many features traditionally supported only by classes, such as computed properties to provide additional information about the enumeration’s current value, and instance methods to provide functionality related to the values the enumeration represents.

なるほど、computed property は普通のクラスみたいに使えるよ、と書いてありますね。ということは、この挙動は別に未定義なわけではなくて、Xcodeでプロジェクト作ってビルドしたときの挙動が「swiftの仕様上ただしい挙動である」と言えそうです。

じゃあなんでPlayground上では仕様と異なる動きをするの?

これ、どうやら Playground が依存している LLDB 上の制限っぽいです。たとえば、以下のような(enumと関係ない)例を見てみてください。

http://i.gyazo.com/05d24ffaaeb3428bb37fc25f7961c230.png

普通にPrintableを実装した Nyan クラスのインスタンスが println に対応していません。そして lldb のなんかの値が吐かれています。つまり、多分これって、静的に解決すべき 「Nyan is a Printable」な関係がLLDBの制限によって静的に解決できなくて、そのせいでうまく動いてない、ということなんでしょうね(仮説です)。

この仮説の正否と、なぜ LLDB 上で Printable が解決できないかについてはおそらく dictav さんがブログで解明してくれるでしょう!期待して全裸待機!!!!