書いて覚えるSwift入門

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

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

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 2022年8月号

2022年7月15日発売
B5判/176ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    設計・開発のイメージが湧く!
    Web APIの作り方
  • 第2特集
    WebエンジニアのためのDNS速習講座
    名前解決のしくみを説明できますか?
  • 特別企画
    「Interop Tokyo 2022」現地レポート
    進化を続けるインターネット技術の最前線をのぞく
  • 特別企画
    MySQL×機械学習 HeatWave MLが変えるデータ活用のかたち
    [後編]HeatWave MLで機械学習のモデル作成・予測・検証を行う
  • 短期連載
    MySQLで学ぶ文字コード
    [2]COLLATIONを正しく理解する
  • 短期連載
    新生「Ansible」徹底解説
    [3]Ansibleの使い方

おすすめ記事

記事・ニュース一覧