書いて覚えるSwift入門

第14回 型にまつわるプロトコルとLiteral Convertible

この記事を読むのに必要な時間:およそ 3 分

本稿を書いている真っ最中の3月22日未明注1)⁠iPhone SEや9.7-inch iPad Proの発表直後にApple製品用の春のアップデート祭りが始まりました。Xcodeも7.3に,そしてSwiftも2.2にアップデートされました。⁠このクソ忙しいときになんてことを」という気持ちが2割,⁠Swift 2.2が本稿に間に合ってよかった」が8割と言ったところでしょうか。

注1)
この連載は,Software Design 2016年5月号に掲載されたものです。

Xcode 7.3 with Swift 2.2

Swift 2.2と2.1の違いは,ソースコード無変更で警告を出しつつも,なんとかコンパイルしてくれる程度の違いではありましたが,Xcode用のSwiftがオープンソース版になったという意味で感慨もひとしお。リリース版のバージョンがLinuxも含めてそろったのは,これが初めてということになります図1)⁠

図1 Swift 2.2リリース

図1 Swift 2.2リリース

しかし無変更でコンパイルが通るとはいえ,手元のプロジェクトではXcodeが警告でかなり黄色くなりました:-p。Swift 2.2では単なる警告でも,Swift 3.0では廃止予定のものが,わんさかdeprectedと警告されます。たとえば「PONS(Protocol Oriented Number System)⁠では,

typealias UIntType:POUInt

が,

associatedtype UIntType:POUInt

になったり,enum Bitが廃止予定なのでIntに変更しろ」とあれこれ20ヵ所前後警告されましたが,Linux版が先行して2.2だったこともあり,Xcodeを7.3にアップグレードして30分足らずで,2.2への移行を完了できました。

性能,とくにArrayまわりの速度が改善されたのは非常にうれしい点で,とくにPONSでは任意精度数値を実装するのに配列を使いまくっているだけあってその効用は大きく,64ビットでもオーバーフローしない20!/19!の計算で,任意精度のBigIntとの速度比較で500倍近くあった速度差が100倍を余裕で切るところまできました。PONSのもくろみはあくまでProtocol-Orientedな数値型を実現することにあり,任意精度数値は「とりあえず動けばOK」だったのですが,これでかなり実用性が増したのではないでしょうか。

Literal Convertible

Swift 2.2の紹介はこれくらいにして,前号の続きです。さっそくですが問題です。iの型は何でしょうか?

var i = 1

正解はIntです。なぜ? 1Intですから。では次のdは?

var d = Double(1)

正解はもちろんDouble1IntでもDouble()に食わせているのですから,最終的にDoubleになるのはごく自然です。では,次のxは?

var x:Double = 1

やはりDoubleです。print(x)してみると,確かに1.0と表示されます。1ではなくて。これはどうでしょう?

var y:Double = "1"

今度はerror: cannot convert value of type 'String' to specified type 'Double'というエラーを出して止まります。しかし,

var z = Double("1")

とすると……,zには無事,Double1.0が代入されるではありませんか。これはいったいどういうことなのでしょう?

DoubleIntegerLiteralConvertibleプロトコルに準拠しているが,StringLiteralConvertibleプロトコルには標準では準拠していない」というのが,その答えになります。

え? 答えになっていない?

では実際にvar d:Double = "1"を受け付けるようにしてみましょう。リスト1のようなコードをvar d:Double = "1"の前にペーストしてみてください。するとあら不思議。今度はエラーにならずにd42.195が代入されています図2)⁠

リスト1 StringLiteralConvertible

extension Double:StringLiteralConvertible {
    public init(stringLiteral: String) {
        self.init(stringLiteral)!
    }
    public init(unicodeScalarLiteral: String) {
        self.init(stringLiteral: "\(unicodeScalarLiteral)")
    }
    public init(extendedGraphemeClusterLiteral: String) {
        self.init(stringLiteral: extendedGraphemeClusterLiteral)
    }
}

図2 Doubleの変数をStringで初期化

図2 Doubleの変数をStringで初期化

リスト1中のLiteral Convertibleとは,その型からの暗黙的な変換をサポートしているという意味なのです。IntegerLiteralConveribleなら整数リテラルからの,StringLiteralConvertibleなら文字列リテラルからの,という風に。⁠暗黙的」というのがポイントです。暗黙的ですので,初期化以外の目的にも使えます。たとえば,

42.0 + "0.195"

はエラーとはならずに,42.195になってしまうのです。

便利といえば便利ですが,濫用するとコードがわかりにくくなってしまいます。標準状態ではDoubleIntegerLiteralConvertibleであっても,StringLiteralConvertibleでないという仕様は,賢明な判断と言えるでしょう。おかげで,

42 + 0.195

はエラーとならずに42.195になる一方,

var i = 42 + 0.195

はエラーとなるわけです。

ちなみに,Swift 2.2には現在で次のLiteral Convertibleプロトコルが存在します。

  • ArrayLiteralConvertible
  • BooleanLiteralConvertible
  • DictionaryLiteralConvertible
  • ExtendedGraphemeClusterLiteralConvertible
  • FloatLiteralConvertible
  • NilLiteralConvertible
  • IntegerLiteralConvertible
  • StringLiteralConvertible
  • StringInterpolationConvertible
  • UnicodeScalarLiteralConvertible

PONSでは,任意精度整数であるBigIntBigUIntIntegerLiteralConvertibleString LiteralConvertibleに,実数であるBigFloatRationalIntegerLiteralConvertibleFloat LiteralConvertibleにそれぞれ準拠しています。おかげでリスト2のように,整数型リテラルでは大き過ぎて表現できない数値の初期化も可能ですし,

var bq:BigRat = 42.195 // (5938418321153065/140737488355328)

という具合に小数で分数を初期化することも可能になっています。

リスト2 整数型リテラルでは表現できない数値を初期化

var bi:BigInt =
"93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000"

ちなみにリスト2のbiですが,実は100の階乗です。import PONSされた状態であれば,

(1...100).reduce(BigInt(1),combine:*)

として手軽に生成できます。もしIntegerLiteral Convertibleでなければ,

(1...100).map{ BigInt($0) }.reduce(BigInt(1),combine:*)

のように,ひとつひとつのIntを明示的に変換したうえで.reduceしなければならなかったでしょう。

フィボナッチ数を計算する総称関数も,リスト3のように書けます。最初のif文のn < 22は,IntではなくTなのですが,わざわざT(2)と書く必要はないのです。

リスト3 フィボナッチ数を計算する総称関数

func fib<T:POInteger>(n:T)->T{
    if n < 2 { return n }
    var (a, b):(T, T) = (0, 1)
    for _ in 2...n {
        (a, b) = (b, a + b)
    }
    return b
}

Swiftの'ミステリー'

リテラルといえば,記号が欠かせません。たとえばStringのリテラルは,""(double quotation mark)で囲まれた部分ですし,{}で囲まれた部分がブロック=関数リテラル,[]で囲まれた部分が添字またはArray/Dictionaryリテラルという具合に。`(backtick)⁠ですら「予約語(keyword)を普通の識別子(identifier)として扱う」ための引用符としての役割を担っています。

その中にあって異彩を放つのが,'(Single quotation mark)⁠です。PerlやRuby をはじめ,多くの言語では「変数展開なし(non-interpolating)引用符」としての役割を与えられているのですが,Swift 2.2時点では単なる文字にすぎません。将来使われるようになるのでしょうか?

そうやって見てみると,あらゆる言語の良いとこ取りをしてきたように見えるSwiftにも,まだまだ一層の発展の余地があるように見えます。どうなっていくのか,お楽しみはまさにこれからでしょう。

Software Design

本誌最新号をチェック!
Software Design 2017年10月号

2017年9月16日発売
B5判/192ページ
定価(本体1,220円+税)

  • 第1特集
    個人でも会社でも使える!
    これだけは知っておきたいGitのキホン
  • 第2特集
    システムのセキュリティチェックをもっと楽に
    脆弱性スキャナVuls入門
  • 一般記事
    サーバーレスで実現!
    素材集サービスを効率化した自動画像管理システムに学ぶ
  • 一般記事
    スキルアップの傾向と対策
    ネットワークエンジニア その技術の極め方
  • 一般記事
    telnetを武器にシステムの本質を理解する
    身近なメールで使われている技術をネットワークコマンドで体験してみよう

著者プロフィール

小飼弾(こがいだん)

1969年生まれ,東京都出身。元ライブドア取締役の肩書きよりも,最近はPokemon GOのガチトレーナーのほうが有名になりつつある……かもしれない永遠のエンジニアオヤジ。

活躍の場はIT業界だけでなく,サブカルからアカデミックまで多方面にわたり,ネットからの情報発信は気の向くまま毎日毎秒! https://twitter.com/dankogai,ニコニコチャンネルは,http://ch.nicovideo.jp/dankogai,blogはhttp://blog.livedoor.jp/dankogai/

当社刊行書籍は『小飼弾のアルファギークに逢ってきた』『小飼弾のコードなエッセイ』など。他にも著書多数。

コメント

コメントの記入