https://www.youtube.com/watch?v=s2fraBK4ZI4
今回は A Swift Tour
から、この前に見終わらなかった「制御構文」の最後のところ「範囲を作って繰り返す」話を眺めていこうと思います。それが時間内に見終わったとしたら、続いてクロージャーの話に入っていこうと思ってます。どうぞよろしくお願いしますね。
———————————————————————————— 熊谷さんのやさしい Swift 勉強会 #40
00:00 開始 00:26 範囲を使って繰り返す 04:26 両端を含む範囲 05:06 CountableRange 06:33 無限に続く範囲 07:44 From 方向に無限な範囲の繰返処理 10:33 負の値からの繰返処理 11:22 範囲を表す演算子 18:48 二項演算子の特殊な記法 23:30 範囲のインデックス 27:45 Strideable 37:07 Strideable としての Double 40:37 stride を使ったループ 42:31 インデックスの範囲を繰り返す 48:34 Start, Last, End 55:24 クロージング ————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #40
はい、今日は制御構文についてお話しします。前回では終わらなかったので、今回は「範囲を作って繰り返す」というテーマについて深掘りしていきたいと思います。この辺りはSwiftで新しく出た表現も多いため、ゆっくりと見ていきましょう。
Swiftでは範囲を作って、その範囲内でインデックスを使用することができます。範囲を作る方法としては、演算子 ..<
を使用して末尾の値を除外したり、演算子 ...
を使用して両側の値を含めたりできます。これにより、インデックスの範囲を作成してループ内で扱えるようになります。この説明が少しややこしいかもしれませんが、実際にコードを書きながら確認してみましょう。
例えば、for
文を使用してインデックスを回す場合のコードは以下のようになります。
for i in 0..<10 {
print(i)
}
このコードを実行すると、コンソールに0から9までが表示されます。このように、..<
演算子を使用することで、範囲を指定してループを回すことができます。
次に、...
演算子についても見てみましょう。同様のコードで試してみます。
for i in 0...10 {
print(i)
}
こちらのコードを実行すると、0から10までが表示されます。...
演算子を使用すると、範囲の両端を含めた範囲を作成することができます。
この他にも、Swiftにはレンジ(範囲)を表現するための型がいくつかあります。例えば、クローズドレンジ(ClosedRange
)や単なるレンジ(Range
)などがあります。また、部分的なレンジ(パーシャルレンジ)も存在しますが、これは特定のケースで使用される特例です。
具体的には、以下のようなコードでレンジの型を確認できます。
let range: Range = 0..<10
let closedRange: ClosedRange = 0...10
これらの型はそれぞれ異なる特性を持っており、用途に応じて使い分けることが重要です。また、無限に続くレンジも存在しますが、これらはシーケンスに準拠していないため、普通のループには使用できません。
以上が、範囲を作成して繰り返すための基本的な応用についての説明です。次回はこれを応用した具体的な使い方についてさらに深掘りしていきたいと思います。 ただ、これは価値観によるかと思うんですけど、例えばパーシャルレンジスルー Int
型といった場合について話します。これはこじつけで全然間違ってることをしゃべるかもしれませんが、Int
型の加減を指定しないところから指定した上限までという表現のときに、厳密に考えれば Int.min
が一番最初という捉え方もできそうな気がします。だから、実質こんな感じという価値観もあってもいいかなという気がするんですが、それよりももっと概念的な捉え方をする人もいるんですね。
多分、明確にそれが min
からカウントしてほしいのであれば、明示的に書きなさいということですかね。そうかもしれないですね。やっぱり一番小さいところがそもそも指定されていないということが大事なんです。
そもそも、特に Int
の場合、人によっては Int.min
から始まると思っている人と、ゼロから始まると思っている人がいるんです。そうですね、本当だ。その違いが確かに曖昧ですね。
例えば、マイナス100からゼロまでにしてみましょうか。swift
ではこういう曖昧さがあるので、明示的に書いてほしいかもしれないですね。これも普通にできるはずです。
let range = -100..<0
このあたり、配列のインデックスとごっちゃにならないように気をつける必要があります。純粋に整数 Int
型の範囲という表現方法なので、こういうことが可能になっているということです。
さっきパーシャルレンジスルーの話をしましたが、空白を入れる場所についても注意が必要です。例えば、
let range = 1...5
これが正しい表現で、空白を詰めて書く必要があります。ただ、パーシャルなレンジのときには、こう書く必要があります。
let range = ..<5
これが個人的に気になります。パーシャルなレンジのときにはここを詰めないといけないというのが、些細なことですが気持ち悪いですね。自分はついついここをスペースを開けて書くので、それが原因でエラーが出ることがあります。
例えば、
let range = ..< 5
このように片方だけスペースを詰めるとエラーが出ます。
これはパーサーの問題かもしれません。swift
では、バイナリーオペレーターに関しては、両側に値を取る演算子です。その場合は空白を挟んで書くか、詰めて書くかという表記が許されているのですが、項を一つしか取らない演算子、アンリアリオペレーターの場合は、詰めて書かないといけない仕様になっています。プレフィックスオペレーターも同様です。
例えば、
let increment = i++
と書くか、
let increment = i ++
どちらも許されますが、見た目が異なります。個人的には統一性が保たれた表記が好きです。
このように、swift
ではオペレーターの取り扱いについて色々な仕様があるので、それを理解してエラーを防ぐことが重要です。 そうだそうだ、これをきっと直せばいいですね。その前にちょっとこの辞書を引いていいですかね。あ、辞書がバージョンを上げたばかりだからか。えーと、あっ、その記号読めないや。あれ?オペレーター?ユーナリーですね。ユーナリーか、全然違う発音してたね、自分が。ユーナリーか、なるほどなるほど。ユーナリーオペレーターって言うんだ、1個だけの。バイナリーの「バイ」とユーナリーの「ユーナリー」ですね。あーなるほどなるほど。ああ、そう言われるとイメージがつきやすいですね。うんうんうん。なるほどなるほど。
で、漢字の話にすると、うん、点が3つならオッケーですね。これは一番最後っていう概念がないから、小なりだとおかしくなるっていうことですかね。無限大の直前までは無限大ですからね。そういうことだ。なるほど。
多分、あれですかね、スペースを詰めないともしかするとバイナリーオペレーターかユーナリーオペレーターか判別しづらいっていう問題があるんでしょうね。例えば一番わかりやすい例はマイナスですかね。あーなるほどね。うんうんうん、はいはい。プレフィックスユーナリーオペレーターか演算子かですね、バイナリーオペレーター。なるほどなるほど、確かに。ここだけならわかります。これだけならわかりますけど、もうちょっと複雑になったときですね。なるほどなるほど。
1 + -1
、こんな感じですね。なんだこれはっていう感じになりますね。あーそうかもしれない。こういうのを嫌っている、パーサーが難しくなるっていうことでしょうね、きっとね。そうですね、おそらくは、ルール付けとね。なるほどなるほど。で、まあマイナスはお馴染みですけどね、これでユーナリーオペレーターのプラスとかを定義したりするとまた混沌としてくるし。うんうん。なるほどなるほど。はい、まぁこんな感じでオペレーターのお話。
あ、もう1個やろう。他にバイナリーオペレーターの場合のもう1個の書き方として、レンジ4として書こうかな。えーと、普通バイナリーオペレーターっていうのは左辺と右辺を取りますよね。それがSwiftでは、演算子を先に書いて、左辺と右辺っていう書き方もできます。できるよね?うん、できます。はい、これ19行目と16行目は全く同じ意味になっていて、これはどういうことかというと、元々ね、Swiftのオペレーターっていうのは関数として定義されているんですよ。実際にファンクションとして定義されているんです。たどれるかな、たどれないか。まぁいいや、関数として定義されているので、それを明示的に関数として使うよっていう時にはこうやって。そうすると、その関数としてバイナリーオペレーターの時には左辺と右辺を順番に引数で取るっていう風になってるんで、同様に左辺と右辺を順番に渡すっていう、そういう動き。
何気なくね、この特徴を使っているのが、よくあるソートメソッドかな。例えば数字が何かあった時に、これをソーティングする。はい、で、例えば標準のように。そうそう、今コメントにもらってるやつ、これがかける。これはね、あの、この勉強会でもお話しした公開関数、ファーストクラスファンクションかっていう価値観。関数も普通の値と同じように引数として渡せるよっていう性格と、それを実際に用いたソーティングっていうメソッドと、オペレーター自体も関数ですよっていう性質がうまく噛み合って、このソーティング sorted(by: <)
のように、昇順で並べるよっていう書き方ができるようになっている。
Swiftって書いて定義にジャンプすれば。Swiftって書いてっていうのは独立して書いてですか?それともこう?こんなことはないか、こうかな。これでまぁ行ってみよう。インポートSwiftって書いて、でそれでファンクションの +
とか見ればいいっていう感じですかね。
あれ?飛べる時ありますよね。飛べる時ありますよね。インポート使う?インポートSwiftしたら、自分はSwiftだけ書いて定義にジャンプしたら、そのまま1行目が !=
でしたね。あぁほんと。じゃあ何か、書く場所にもよるのかな?関係ないはずだとは思いますけど、プレイクラウドとかと違いますし。今プレイクラウドで読めますからね。確かにその違いも何かありますよね、微妙にね。とりあえずこの演算子が飛べる時、飛べない時があって。まぁいいや。 えーっと、こういう風にね、ちょっとややこしいジェネリクスになっていますが、要は関数で、これに丸括弧をつけることで普通の関数として使えるのです。これで最初と最後を取るということですね。
オペレーターに関しては、選べる名は使われないという感じですかね。レンジでも大小比較できるんだと言われてみると、そうですね。でも、レンジで大小比較ってなんかピンとこないですね。どういう大小比較なんだろう、小さいかどうか。あ、でもインデックスですね。インデックスがついている。クローズドレンジのインデックス。クローズドレンジってそうか、インデックスを持っているのですね。なるほど、レンジのインデックスで比較できるのね。びっくりしました。レンジにインデックスね。これをわざとマイナス100からとかにしてあげればいいんだね。すごい気持ち悪い気がしちゃうけど、当たり前か。レンジ1の0番目とかできるってことでしょ。あ、プリントしないとダメだ。えーっとこうね。おー、ちょっと余計なのが表示されてるな。あ、まあまあまあ、分かれば十分分かりますけどね。ゼロが出た?ゼロが出たってどういうことだ?1は?あ、1。じゃあマイナス100とかなってるのもしかして。あ、そうでした。なんか直感的じゃなかったですね、自分の中で。そうかそうか、なるほど。配列は違和感ですね。これは多分いらないのかなとかちょっと思っちゃったけど、これをインデックスで管理するんだ。
じゃあ例えばダブルとかだったらどうなるんですかね。あ、ダブルね、どうなるんだろう。とりあえずタイプを確認しよう。昔はそのあたりがカウンタブルレンジか否かで判断してたんですけど、今どうなっているんだろう。今はエレメントがストライダブルに適合しているかどうかっていうインデックスの条件は見られるはずから。レンジと、とりあえず定義見ずにやってみましょう、ランタイムで。こうしてあげて、すると、あーなるほどね、あれだ。あーなるほどなるほど、よくわかりました。えっと、ほら、なんとかコンフォーマンス、コンディショナルコンフォーマンス、あれを使ってるんですね。なるほどね、ストライダブルに適合していないからできないんですね。なるほどね、そういうことですね。昔はコンディショナルコンフォーマンスがなかったから、ストライダブルなものを要素に持つレンジはカウンタブルにしてって、そうじゃないやつは普通のレンジにしてたんですね。なるほどね、ちょっと難しい話ですけど、まぁ、だんだんと時期に分かることだと思うので、分からない人も軽く聞き流すぐらいにしてもらったらいいかなと思います。
コンフォーマンスすごいな、これはサインドインテジャーとか指定されてるけど、サインドインテジャーで縛ってるけど、まぁストライダブルで良さそうですよね。まぁいいや、イント系、というかイント系以外でストライダブル適合されてるものはストライダブル。文字列ってどうでしょうね。キャラクターか、あれもイントみたいなものか。そういう時に便利なのが、ストライドメソッドってストライダブル取りますよね。ストライダブル取って、これでAからZまでいけますか。バイはとりあえず抜きにしよう。ダメか、キャラクターもダメですね。定義を見ないとダメか。Swiftではキャラクターはダブルコーテーションでしたっけ、シングルコーテーションという構文はとりあえずなくて、あと当てないとダメか、型を明記しないとダメかもしれないですね。言語によってはキャラクターはシングルコートみたいな書き方する言語ありますけど、Swiftではダブルコートでキャラクターとして認識している節がありますからね。キャラクターなんてなかったですね。キャラクターもストライダブルじゃないですね。ユニコードスカラーとかになるのかな。そっか、文字コードは確かにそうですね。キャラクターをストライダブルにするわけにはいかない。ユニコードスカラーもダメなんだ。ユニコードって規格で順番があるから、ストライダブルでも良さそうですけどね。まあいいや。
ストライダブル、この定義たどると何か書いてあったりするかな。デート?デートがストライダブルって本当なのか?ちょっと後でやってみます。ユニックスタイムでストライドするのかな。 とりあえず、こうやってコメントがやたらと細かく書いてあって、この中でStrideable
に適合しているものは何なんですか、みたいなのが書いてあったりする。まあ、今書いてあったっぽいですけど、あれ? ストライドで不動小数点数をやってる? あ、別のストライドがあるのか。うんうん、Dateもどうなんだろうな。まあ、ちょっと見てみますか。これぐらいにして…
Dateということはインポートがいる。import Foundation
でDate。Strideable
でプリント、実行…。Strideable
、ジェネリクスか、そっかそっか。定義をたどるしかないか。ストライド、これでextension Date
。なのなしが続いて、今のDateがStrideable
っていうのは、ちょっと早合点したかもしれないですね。特にないかもしれない。本当にフォーマットスタイルとかあるのはFoundationだからかな。まあいいか、とりあえず。
あれ? まあいいや、ストライド。えーとストライド、ストライド、from-to-by
。Strideable
以外があるのかな。定義がたどれない。とりあえず不動小数点数も0.1刻みでやるよって言ったら売れて、それで定義をたどると、これはStrideable
じゃないものを受け取る。うんうん、なるほど。
ん? 違った。AがStrideable
だったら…あれ? Double
ってStrideable
でしたっけ? Strideable
って言うんだったか、こうか。なんか自分がStrideable
をちょっと勘違いしていたかもしれないですね。ドキュメントが書いてある、なるほど。
ファンクEが、ファンクアクションE、Strideable
でVとTでアクション、エッティング。うんうん、全然自分が間違ったんだ。あの、さっきのレンジの話。やっぱりエラーで言ってた。これを一応コメントアウトしてビルドを取るか見てみますかね。大丈夫ね、やっぱりこれをじゃないと。Strideable
。あれ? ここエラーじゃなかったっけ? ちゃんと動いてるね。そこはエラーじゃないですね。ここエラーじゃないか。このsubscript
の引数が取れるかどうか。
subscript
の引数が取れるかどうか。今25行目の。なぜエラーだったのか。そっか、こっちがエラーだったのか、なるほどね。間違えた。signed integer
。これは、多分インデックスがInt
になっていてっていうことか。ストライドがInt
じゃないか。じゃあゼロは何が取れるとか気になりますよね。subscript
がそもそもないのか。
多分、おそらく定義を見るとsubscript
の定義がsubscript
が取る引数がsigned integer
で、そのためStrideable
のストライドもsigned integer
じゃないと成立しない。そうなんですね、なるほど。じゃあそっか、このsubscript
アクセスができるのはsigned integer
、要はinteger
系独特の機能なのね。なるほど。
いるのかな、使ったことある人いますかね、これ。使い道ちょっと微妙なことはないですね。でもちょっと面白いですね。こんなのができるってのを考えてなかったから使っていないっていうのも、十分にありそうですから。ってことは、多分これ自分でStrideable
の型を自分で何か作って使えるんですね、つまりは。ああ、そうですね。後で試してみよう、なるほど、なるほど。
ちょっと面白いかもしれない。この辺り興味が湧いた人いらしたら、ぜひ活用方法を模索してみるのもいいかもしれないです。そっかそっか、だからStrideable
。ダブルもStrideable
だからこの範囲が回せるんだ。でもダブルってStrideable
じゃないはず。Strideable
という価値観は難しい気がするんですけど、どう動くんでしょう。こう0.0
から0.1
までどう回りますかこれ。想像できる方いらっしゃいますか? 動かしてみよう。
あ、エラーか。ダブルストライド、これはダメなんだ。これも多分あれですね、要はストライドがsigned integer
に適合していないから。うんうんうん、ああなるほど、ああそうなんだ、ああそういうタイプね。なるほどなるほど。だからダブルでストライドするためにはストライドメソッドを使って、stride(from: 0.0, to: 0.1, by: 0.01)
みたいにしないといけないということですね。なるほど。
これを省略すると同じエラーね。あ、そうね、絶対にこの引数を取らないといけないタイプになっていて。これで0.01
ずつみたいにすると、やっと動くようになるという感じですね。Strideable
あたりがややこしくなっている印象がします。個人的に。これはコンディショナルコンフォーマンスでかなりテコ入れがされて、その代わりなんか微妙な感じがするような、しないような。
stride
、これです。by
の後がステップですけど、ステップを指定しないとstrideable
って呼ばないほうがいいんじゃないかなって個人的には今見てて思ったんですけど、どうですかね。今だとダブル型はStrideable
になっていて、そのストライドのいわゆるステップがsigned integer
かどうかっていうね、そういう価値観になっているようで、なんか個人的に少し難しい、無理があるような気がします。 これから話すのは、Swiftの Strideable
プロトコルについてです。Strideable
では「ステップ」という概念を「ストライド」と呼んでいます。他の言語では「ステップ」と呼ばれるものが、Swiftでは「ストライド」となるんですね。
早速話を戻しますと、Swiftでは範囲を使ったループが可能です。基本的には Range
型を使ってループを回していくのが基本スタイルです。しかし、この範囲を指定する方法として「ストライド」という概念が存在します。このストライドの概念を活用すると、グローバルに定義された stride
関数を利用して、自分の好みに応じてステップを刻んでいけます。
例えば、-100
から100
未満までを2刻みで実行するといったことも簡単に書けます。ただし、注意点として、through
は 100
を含みますが、stride(from:to:)
の場合は100
を含まないのです。ここはちょっと混同しやすい点ですね。
そして、インデックスアクセスについても触れておきます。スライドに書いてあった例では、 i
を 0
から 4
未満まで回して足し合わせる例がありました。ですが、ここで伝えたいポイントとしては、インデックスを使って処理を行いたい場合についてです。
例えば、値 value
を持つ配列があって、これらをすべて足し合わせたいときにインデックスを使う場合です。もう少し複雑な例としては、Shape
というインスタンス型があって、これが draw
メソッドを持っているとしましょう。異なる形(四角形、三角形、円など)を配列として持っているとします。
let shapes: [Shape] = [Square(), Triangle(), Circle(), Square()]
この配列の要素に対してループを回していく場合は、以下のようにインデックスを使えるわけです。
for index in shapes.startIndex..<shapes.endIndex {
shapes[index].draw()
}
このように、インデックスの範囲を作って、そのインデックスをループ内で使用することができます。要点を押さえるため、もう一度整理しておきますが、インデックスアクセスは範囲と共に使用でき、インデックスを使用して配列内の要素にアクセスすることが可能です。
以上が、基本的な範囲の使い方とインデックスアクセスの例に関する説明です。次に、さらに詳細なテーマに進む前に、コメントをいただいた ジェネリクス
についても少し触れておくと、確かに難解な部分が多いので、ゆっくり理解することが重要ですね。詳細なコードは後で確認するとよいでしょう。 この書き方について、せっかくなのでいろいろ紹介しようかなと思うんですけど、もっと簡単に書けるようになっているというお話です。その前に、もう一個面白い話が出てきているので、先に余談として話します。
スタートインデックスからエンドインデックスまでの間を表現するとき、ここで分かっている人にとっては当たり前の話ですけど、スタートは最初、エンドは最後の要素の次です。ここが結構大事な考え方で、何かと最後の要素の次を表現するときにエンドインデックスやエンドという言葉がプログラムの中でよく使われる印象です。
この「最後の次」というのを別の言葉で表現すると、日本語以外では何だろう?とりあえず「番兵」(英語では「sentinel」)という言葉が昔から使われています。情報処理の基本情報処理とかには、たぶん番兵を活用していろいろループをしましょうみたいなのが今も出てくるんじゃないかなと思います。C言語の文字列の最終端がゼロであるというのもエンドインデックス的な番兵ですね。ゼロが現れるまで繰り返してあげればうまくいくという発想です。「エンドが出たタイミングで処理を打ち切ってあげるとうまくいく」、つまり「最後の要素の次」にしておくと話がうまくいくんですよ。
例えば、while loop
とかをイメージしてみてください。ただし、while location != end
のように書いてください。最後の要素そのものではなく、次の要素までを範囲に含めるためです。
さて、最後の要素そのものを指す場合、プログラミングでは一般に「ラスト」(last
)が使われますね。last index
というのはないかもしれませんが、この場合、最後の要素それ自体を指すことになります。そういった発想を持つときには、例えばファイル操作での繰り返し処理の場合、「Repeat File」というよりも「Repeat While」の方が相性がよくなってくるかもしれません。Repeat While location != last
といった具合ですね。
今ちょっとつなぎ直して動かしていますので、ちょっと待ってくださいね。では、もう少し話を進めます。
配列にはインデックスの範囲があって、最初からindices
という変数が用意されています。indices
はインデックスの複数形で、「インデックスの集合」を指します。Appleはindices
を採用しており、これを利用すると、配列のインデックスに基づいて処理を行うことができます。たとえば、次のように使用します。
for i in shapes.indices {
// shapes[i] を使った処理
}
このように、スタートインデックスからエンドインデックス未満までと同じ動きをします。これで、インデックスを取ってshapes
に対して同じように処理ができます。
さらにおまけですが、indices
が範囲になるとindices
のstart
やend
が使えます。今思い出しましたが、これらはfirst
やlast
のようなものでしたね。
例えば、indices
のlast
はオプショナルな値であり、存在しない場合もあります。ですから、こうした場合には注意が必要です。
基本的な話ですが、もし迷ったら、エンドなのかラストなのかを意識して名前を付けると良い感じに仕上がります。
はい、今日の範囲のループの話はこれで終わりです。1時間のいい感じの時間になったので、これで終わりにしましょう。ありがとうございました。お疲れ様でした。