Sequenceプロトコル
前回はWWDC特集だけあって、Swift 4を含め現行のSwift 3ではまだ実行できないコードも多く登場したのですが、今回は順序どおりすべてSwift 3で実行できるコードを書いていきます。
そう、順序。Swiftで順序といえばSequence
プロトコル。Array
もDictionary
もRange
もすべてこのSequence
プロトコルに準拠しています。
コンピュータ言語における順序の歴史
コンピュータプログラミングとはものごとを順序よく片付けるために存在するといっても過言ではなく、たいていのプログラミング言語にはそのための構文糖衣が用意されています。いにしえの行番号付きBASICですら、こんな感じに。
なぜ構文糖衣かといえば、「順序よくやる」は単に「終わりが来るまで次をやる」のと同じことだからです。C言語はこのあたりはっきりしていて、
は、
と等価であることがはっきりとわかります。
しかしより現代的な言語では「順序よくやれ」という場合に「いつ終わるのか」「次とは何か」を省略して書くのが流儀となっています。たとえばPerlなら同様の繰り返しは、
と、「0から41まで」という言い方で、「0から1つずつ増やしていって42だったら終了」という言い方を避けています。
Swiftも、かつてはC言語風のfor
ループが存在しました。
しかしこれはSwift 2で廃止され、
という「0から42の手前まで」(あるいは0...41
と書いて「0から41まで」)という書き方に統一されました。
なぜそうなったかといえば、そのほうが直感的で間違いが減るから。終了条件で<と<=
を違えてしまうというのは、今でもしばしば脆弱性の原因となったりもします。
それでもSwift 1にはC言語風のfor
が残っていたのははなぜでしょう? Sequence
プロトコルが未熟だったからです。このプロトコル一時期はSequenceType
に改名されていたりと言語設計者も迷った形跡が見られますが、Swift 3にいたってそのあたりの「ぶれ」もなくなり、Swift 4のSequence
もSwift 3と互換です。
Sequenceの正体
それではSwiftにおけるSequence
とはなんなのか? 「書いて覚える」という視点では、次の2つが等価であることさえ覚えておけば良いでしょう。
つまり、.makeIterator()
という「イテレーター」を返すメソッドを持ち、そのイテレーターに対して.next()
を呼ぶことで、順序よく値を取り出し、.next()
がnil
を返したらおしまい、というわけです。
それでは実際にInt
をSequence
にしてしまいましょう。
こんなので実際に動くのでしょうか?
動きました!
しかし、そのためにIntIterator
というStruct
を作るのもなんだかめんどうです。この場合は次のようにしてまとめられます。
それでもC言語のfor
に比べてめんどくさそうに思えます。しかし、わざわざSequence
に準拠するだけの価値は確かにあるのです。こうしておくことで、.map()
や.filter()
や.reduce()
が無料で手に入るのです。
Swiftにおいて型をSequence
に準拠させるということは、RubyにおいてそのClassをEnumerable
にすることに相当します。RubyにおけるEnumerable
の発展を見れば、SwiftでSequence
に準拠させることがどれほどの利益をもたらすかをあらためて感じ取れるのではないでしょうか。
とはいえ、さすがにInt
をSequece
にするのはやり過ぎでしょう。RubyでもInt
クラスはEnumerable
ではないのですから。しかしRubyのInt
には.times
メソッドが存在します。SwiftのInt
に.times
は標準装備されていませんが、次のとおり簡単に追加できます。
より実践的なSequence
Sequence
が真の威力を発揮するのは、単一の値ではなく複数の値をまとめて扱うときにあるのは、.map()
、.filter()
、.reduce()
の例のとおりです。Swiftにはすでに標準でArray
を持っていますが、たとえば5,000兆個のデータをArray
に読み込んでというのはメモリが足りなさ過ぎるでしょう。そういう場合にもSequence
に準拠した型としてそれを実装すれば、少しずつストレージから読んで処理するのも「いつものやり方」で実現できます。
ここではLispやHaskellでよく用いられている片方向リストを実装してみましょう。こんな感じですか。
最低限のアクセサーと……、
………イニシャライザーを用意しておきます。
この状態で、
とすれば確かにl
は0->1->2->3->Nil
という具合に初期化されて、print(l)
してみると、Pair(head: 0,tail: __lldb_expr_80.List<Swift.Int>.Pair(head: 1, tail: __lldb_expr_80.List<Swift.Int>.Pair(head: 2, tail: __lldb_expr_80.List<Swift.Int>.Pair(head: 3, tail:__lldb_expr_80.List<Swift.Int>.Nil))))
なんて感じになっているのですが、これをSequence
にするにはどうしたらよいのでしょう? 先ほどの例のようにListIterator
を追加しても良いのですが、実はSwiftには次のような簡単な方法も用意されています。
こうしてからfor v in l { print(v) }
してみると、確かに値を頭から取れていることがわかります。
あとはこれを利用して、Array化したり……、
もっと見やすいよう文字列化したりするのは楽勝です。
次回予告
今回はSwiftがSequence
プロトコルを通してどのように「順序」というものを抽象化しているかを見ていきました。しかし世の中は順序どおりにいくとは限らないもの。その場合には、どうしていくのか。
次回はCollection
プロトコルを通してそれを見ていきます。
- 第1特集
MySQL アプリ開発者の必修5科目
不意なトラブルに困らないためのRDB基礎知識
- 第2特集
「知りたい」「使いたい」「発信したい」をかなえる
OSSソースコードリーディングのススメ
- 特別企画
企業のシステムを支えるOSとエコシステムの全貌
[特別企画]Red Hat Enterprise Linux 9最新ガイド
- 短期連載
今さら聞けないSSH
[前編]リモートログインとコマンドの実行
- 短期連載
MySQLで学ぶ文字コード
[最終回]文字コードのハマりどころTips集
- 短期連載
新生「Ansible」徹底解説
[4]Playbookの実行環境(基礎編)