https://www.youtube.com/watch?v=yhAVDj8b_C4
今回は、前回に話した A Swift Tour
の「制御構文」として紹介されている switch
について、その「パターンマッチ」に関するところを眺めていきます。パターンマッチといえば 正規表現
を思い浮かべる人が多いと思いますけれど、Swift はそれとは違ってシンプルながらも様々な場面に対応できるものとして用意されています。そんなあたりを見ていこうと思いますので、どうぞよろしくお願いしますね。
—————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #35
00:00 開始 01:04 Language Reference 01:48 パターンマッチング 02:39 正規表現とは異なるもの 04:05 パターンとは 06:31 失敗するかもしれないパターンと失敗しないパターン 07:27 パターンを使える場所 09:23 どんな種類の値にもマッチするパターン 09:59 分割代入 14:55 どんな種類の値にもマッチするパターンの種類 16:08 型を明記してマッチする値を制約可能? 25:35 値にマッチしない可能性のあるパターンの種類 27:25 完全なパターンマッチング 29:41 8つのパターン 30:32 ワイルドカードパターン 32:16 識別子パターン 36:30 ワイルドカードパターンと代入式 39:51 値束縛パターン 43:10 値束縛パターンで var を使う 44:49 タプルパターン 47:21 タプルパターンと型キャスティングパターン 51:07 次回の展望 ——————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #35
今回何をやろうかなっていう話を、前回の終わりの頃に少し触れましたが、やっぱりパターンマッチングは結構大事なところなので、とりあえずやっておこうと思いました。また、パターンマッチングだけで特別な回を設ける必要はわざわざないと思い、今回の流れを変えてやってみることにしました。この時間内で「特別回」として先取りする感じでやってみようかなと思います。
意外と調べてみると、「The Swift Programming Language」の中にパターンマッチングについて、特にスイッチ文のパターンマッチングについて書かれた項目があります。それがランゲージリファレンスの中で結構後に回されているので、これを今のうちに紹介していこうと思います。今日で終わるか次回に持ち越すか分からないぐらいのボリュームになっていますが、終わらなければ次回で完結するという感じで、2回ぐらいやれば終わるかなという雰囲気です。まあ、やってみないとわからないので、とりあえず今日はパターンマッチングのお話をしていきたいと思います。
まず、パターンマッチングと聞くと、正規表現というものが思い浮かぶかもしれませんが、Swiftのパターンマッチングはそれと比べて相当シンプルにまとまっています。パターンマッチングと聞いて複雑な高度なことができると想像すると、若干間違いかもしれません。ただし、シンプルな構造でありながら意外と複雑なこともできるので、その辺りを見ると面白いです。
大事なポイントとしては、世間で言われるパターンマッチングとSwiftで言われるパターンマッチングを混同しない方が理解が進むというところです。これは大事にしてほしいと思います。
今パターンマッチングを何でやっているんだろう、という話です。昔はPerlという言語がパターンマッチングに非常に向いていました。個人的には今も使っていますが、それがJavaScriptとかでも結構簡単に書けるようになってきています。要するに、意外といろんなもので正規表現は純粋に書けるようになっています。
Objective-Cだと結構大変でした。「NSRegularExpression」というクラスを使って文字列型としてパターンを書かないといけなかったので、エスケープシーケンスなどいろいろありました。しかし、Swiftのパターンマッチングは全然違うものになっています。
大事なポイントとしては、この今表示中のスライドの一番下にあるように、パターンが基本的にタプルとして表現されているというところです。ただし、それだけではないです。タプルとして表現されているところを捉えると面白い見え方がしてくると思いますので、頭の片隅に入れておいてもらえると良いかなと思います。逆に、パターンをタプルとして捉えすぎるとおかしくなるかもしれないので、適度に気にして見ていってください。
まず、パターンとはどういったものかというと、Swiftにおけるパターンとは一つまたは複合的な値の構造を表現するものです。これを理解していくと腑に落ちるところがあるので、抑えておくと良いと思います。
一つまたは複合的な値、すなわちタプルですよね。一つは除外されちゃいますが、そんなノリです。値の構造を表現して、どんな構造の値かを判定していくのに使えるのがパターンです。様々な値にマッチさせることができ、全体としてマッチさせるだけではなく、その一部に対してマッチさせることも可能です。
少しややこしい表現になりますが、正規表現が得意な人はイメージしてほしいのがサブパターンです。丸カッコで作るとそこだけを抜き出せるみたいなイメージに近いです。それが正規表現ではなく、複合的な値の構造の中からこの部分だけをマッチさせることができるというふうに解釈してもらえるとイメージしやすいと思います。
そのパターンには大きく分けて二つの種類が存在します。 今回、自分がこの資料を整理するまであまり意識していなかったんですけど、この2つを意識すると面白いし、わかりやすいかなと思いました。
まず、どんな値にもマッチするもの、つまり失敗しないものと、実行時に指定された値にマッチしない可能性があるもの、要はマッチに失敗する可能性があるものに大別できます。
これらのパターンは do
の catch
文や switch
文のケースラベル、 if
、 while
、 guard
、 for in
の条件など、様々な場所で使えます。実際、そのように使われています。したがって、パターンマッチングは様々な場面で使えるというのが大きなポイントです。他のプログラミング言語でも正規表現を使う場面は限られていますが、Swiftのパターンマッチングは広範囲で使えるんです。
また、資料を読み進めていくと、代入文もパターンマッチングの一環と捉えることができるとあります。つまり、パターンマッチングを理解しているとSwiftの言語表現の幅が広がるわけですね。ですから、どこで使えるかという前に、まずパターンマッチングにはどんな種類があるかを押さえておくと良いでしょう。さて、これから具体的なパターンについて見ていきます。
まず、どんな値にもマッチするパターンについてです。大きく分けたうちの一つ目には4つのパターンが分類されます。全部で8つのパターンのうち、4つがどんな値にもマッチするパターンとして規定されています。このパターンは組み合わせて使うことができ、表現の幅が広がります。
実際にこのパターンを見ていくと、単純な変数や定数、オプショナルな値(変数や定数の分割代入)があります。分割代入とは、一部の部分にマッチするという解釈で、複数の値を一気に代入する書き方のことを言います。タプルを使うと非常に自然な書き方になります。
では、分割代入の具体例を紹介します。基本的なパターンとして、以下のようにコードを書きます。
let (x, y, z) = (1, 2, 3)
このように、3つの Int
を持つタプルに対して、3つの値を一気に代入します。これが分割代入の一例です。このコードでは、それぞれ x
、 y
、 z
に 1
、 2
、 3
が代入されます。
もう少し分かりやすい例を見てみましょう。
let (a, b, c) = (4, 5, 6)
このように書くと、同様に a
、 b
、 c
に 4
、 5
、 6
が代入されます。そして、もちろん a
、 b
、 c
はその後も自由に使えます。これが一番しっくりくる分割代入の例でしょう。
さらに興味深いこととして、既存の変数に対して再代入を行うこともできます。たとえば、以下の例では d
と e
に対して、新しい値 7
と 8
を代入します。
var (d, e) = (0, 0)
(d, e) = (7, 8)
これにより、 d
と e
は新しい値 7
と 8
に置き換わります。これも一種の分割代入です。
このように、分割代入はいろいろな形で使えるので、Swiftのパターンマッチングの一環として理解しておくと役立ちます。 とりあえず、こうやってやることもできます。他にもできるのかな。これは自分があまりやったことがないからわからない。パターンマッチングだとできる構文になるんですけど、代入だとどうなんでしょうか。さすがにエラーになるのかな。これは、これはエラーになりますね。なるほど。このあたりの書き方はまたバリューバインディングパターンのところで紹介しますが、そうすると13行目までのこんな感じか。このあたりが分割代入のイメージをつかむ例になりますね。
では、戻ります。そういった分割代入でパターンマッチが使われます。で、この区分に該当するパターンとして、ワイルドカードパターン、識別子パターン、値束縛パターン、タプルパターンという4つのものが存在します。
どっちのほうが通りがいいのかな。この英語と日本語、ワイルドカードパターン、アイデンティファイアパターン、バリューバインディングパターン、タプルパターン。どっちが一般的によく聞く言葉かはわかりませんが、いずれにせよあまり聞かないかもしれません。もしかするとこのあたりは「switch文」に精通している人がいるかどうかで、この言葉をよく聞くかどうかが分かれてくるのではないかと思います。しかし、この4つのパターンの言葉からどんなパターンであるかを理解すると、パターンの使い方がかなり変わってくると思います。この勉強会ではしっかりとこれを押さえていこうというのが、今回の大事なテーマになっています。
それで、これらのパターンに型を明記してマッチする値を制約することも可能と書いてあって、「ああ、そうなんだ」と自分は思いましたが、本当に書けるのかまだ半信半疑です。ちょっと試してみましょう。
let value = 10
switch value {
case let x as Int:
print(x)
default:
break
}
細かい話はこれから説明しますが、value
でワイルドカードパターンで型明記できるのか。as Int
と書けますかね。動くかどうか試してみます。でも、これはコンパイルエラーになりますね。違う違う、こういう書き方じゃないって書いてあったな、そういえば。コロンかな。アンダースコアではなくて、case
、as
で書けるのではないかと思ったんですよね。しかし、ここにコンマが抜けていました。アンダースコアの指摘ありがとうございます。すっかり忘れていました。やっぱり書けていませんね。
アンダースコアのas Int
って書けますかね。as Int
はできるのですよ。でも、コロンを使うとswitch
の終端と被るので、全然変なことをやっているなと思いました。
型を明記してマッチする値を制約することも可能です、と書いてあって。こういう風に書くのかな。switch
文は非常に紛らわしいですね。as
を使わないで書かないといけないんですね。あとで自分で調べないとダメですね。書ける気がしないんですよね。
とりあえず、ちょっと文章を出してみましょう。なんか違う場面のことを言っているのかな。ちょっと実験中ですね。ドキュメントで、Language Guide
やLanguage Reference
の中にパターンマッチのところがあって、それでこの中に...
えーっと、どこだったかな、ここら辺ですね。今、どこの話だったっけ。ここか。後ろのほうに書いてあったのを持ってきたんだっけかな。
最初の項だな、ここですね。you can specify
ってところ、これがタイプアノテーション、これらのパターンにマッチするもの、その指定したタイプにのみマッチするものを型アノテーションしてねっていうことが書いてありました。
このパターンって言っているのが、ワイルドカードパターン、識別子パターン、バリューバインディングパターン、タプルパターンって書いてあります。タイプキャスティングパターンはこのあと出てくるんですよ。つまり、これらに対して型アノテーションが可能って書いてあって、そうだったかなと思って。
バリューバインディングパターンについては、解釈の仕方によってちょっと違いがありますね。アンダースコアに気づいたら、アンダースコアのことを指していますね。タプルとかで型フォームCがワイルドカードアンダースコア、例えば(x, y)
みたいなタプルで、let case let (x, _)
とか書くと、それがワイルドカードパターンってことになりますね。アンダースコアを使って。
これは、ループ文で例えば1から3
を回したときに、数値の値がいらないときはアンダースコアを書いて、それがワイルドカードパターンになります。ワイルドカードパターンに型アノテーションできるかどうか、まさにlet x as Int
のx
をアンダースコアにしたら、どうなりますかね。 そう、それはタイプキャスティングパターンという、また別のパターンがあるんですよ。今ちょっとワイルドカードパターンに対して型アノテーションができないかっていうのを個人的な好奇心でやっているところなんですけど、やっぱり書けないですよね。
そうですね、クロージャー的な書き方だと、こんな感じに近くなってくるわけですけど、やっぱり書けないよね。こういう書き方はできるんですよ。これはできますね。しかし、「The Swift Programming Language」にずばり書いてあるけど、意外とそうじゃないってことがある。この後できないんだ。これも「The Swift Programming Language」に書いているんです。
よろしくお願いします。あとは、このタプルの回路ですけど、_ as T, x as Int
中だとできるかも。ここできそうですね。これはできそう。あ、実行されない。そうですね、大丈夫なはず。今ちょっと実行中ですけど、ワーニングで済んでますね。
そう、そういう感じですね。なんか制約がちょっとありそうですね。タプルパターンという書き方なので、もしかするとプレイグラウンドが実行しなくなっちゃった。まあ、いいか。とりあえず置いておきましょう。多分ワイルドカードパターンで型アノテーションできないです。多分ね。あ、これはできたって言ってたか。でも今の話がそうか。
ワイルドカードっていう意味だと、_ as Int, case x as Int
はOKですよ。これがワイルドカードっぽいじゃないですか。でも、それはね、ワイルドカードパターンではなくて、タイプキャスティングパターンなんです。混合できるんでね、難しいんですよ。解釈が意外と曖昧だったりとかして。
プレイグラウンドがちょっと動かなくなっちゃってるのが悲しいな。後でちょっと自分でやってみようかな。この as Int
、今警告で済んでるじゃないですか。でもダメなんだ。これがタイプキャスティングパターンなんだ。タイプキャスティングパターンだからダメって。
自分もこうやって喋りながら混乱してる。ワイルドカードパターンとタイプキャスティングパターンを複合したものがこれですね。いいや、ちょっとラチが開かない気がするから次行きましょう。それぞれ見ていく中で、タイプアノテーションできるかどうかを見ていくことにしましょう。
で、とりあえず次。大別して2つって言ったうちのもう一つ、指定した後に実行時にマッチしない可能性のあるパターンとして、そう、列挙パターンとオプショナルパターンと評価式パターンとタイプキャスティングパターンが出てきた。この資料を書いていて自分も思ったんですけど、型アノテーションした時点でマッチしない可能性出てきますよね。そうすると前に紹介した「どんな種類の値にもマッチするパターン」じゃなくなってくる気がしませんか?
なので、「The Swift Programming Language」の最後に書かれていた「これらに型を明記してマッチする値を制約することも可能」という表現が、なんか語弊を含む表現かもしれない。というか、語弊を含まなかったとしたら間違っている書き方になっていて。やっと何か言いたいことがわかってきた気がする。これに次に紹介するパターンを加えて制約を加えていくことも可能ですよ、と言いたい気がしてきた。
これね。なので、そうしていくと is
演算子や as
演算子をアンダースコアに併用して使っていくみたいな話になってくる。このような解釈の方が正しそうな気がしてきました。
はい、まあそんなつもりでここを見ていくと、この値にマッチしない可能性が出てくるパターンは、完全なパターンマッチングで使われる、っていう風に書いてあった。この「完全なパターンマッチング」という表現、非常に曖昧な気がするんですけど、要は正規表現みたいな、マッチさせて値を取り出すとかね、そういったマッチするか否かとか、いろんなそういう場面で、一般に想像されるパターンマッチングで使われるパターンであるよ、っていうことを言いたそうな気がしています。
大事なところとしては、実行時に値が存在しない、マッチする値が存在しない可能性があることによって、マッチした場合はこれ、マッチしなかった場合はこれ。また、別のパターンでね、マッチしなかったらさらに別の条件分岐として使えるパターンになってくる。制御構文的なパターンになってくるのが大事になってくるのかな。
これら4つのパターンを使うことで制御を行っていけるっていう雰囲気ですかね。 正式表記としては「イナミレーションケースパターン」「オプショナルパターン」「エクスプレッションパターン」「タイプキャスティングパターン」などの言葉が割り当てられています。どちらを使うかは好みで選んでよいと思いますが、これらの言葉を意識して使わないと聞いたときに馴染みがないので、慣れておくことをお勧めします。スイッチ文は基本的にパターンマッチングの文法ですが、スイッチ文を最も効果的に活用するためには、パターンの理解が重要です。
それでは8つのパターンについて解説していきます。ワイルドカードパターン、アイデンティファイアパターン、オプショナルパターン、エクスプレッションパターン、タプルパターン、バリューバインディングパターンなどがあります。この8つのパターンを覚えておくと、スイッチ文のスキルが向上することでしょう。上手にこれらを使えるようにするのは非常に有意義な学習方法です。
まずはワイルドカードパターンから見ていきましょう。ワイルドカードパターンは _
(アンダースコア)で表現されます。特徴としては、どんな値でもマッチするが、マッチした値を無視するというものです。これは条件分岐でパターンに一致したときに、値に関与せず何かを行いたいというときに使います。
次に、アイデンティファイアパターンを見ていきましょう。これは変数名や定数名に結びつけるときに使うパターンマッチです。理解が難しい場合もありますが、基本的にはバリューバインディングパターンのサブパターンとして使われることが多いです。サムバリューなどの変数名が識別子パターンとなり、任意の値にマッチします。例えば、次に示すコード let someValue = 42
は、変数 someValue
に対して、42という値を結びつける識別子パターンの例となります。このように、パターンマッチを通じて変数に値を代入するのは、スイフトにおける基本的な概念です。
このようなパターンマッチの理解は、基本的な文法に留まらず、コードの可読性や柔軟性を高めるための重要なスキルです。ぜひマスターしていきましょう。 バリューバインディングパターンとは違うので、そう考えるとアイデンティファイアパターンの存在意義がこの式の場合は見えてくる感じがしますね。
コメントが寄せられました。「ワイルドカードパターン」。これは先ほどの一個前のパターンで、_
(アンダースコア)を使った場合、全てにマッチしてしまいます。つまり、全ての変数または無視する、ということになります。なるほど、面白い解釈ですね。確かにそうです。そして、それができるのかというのに非常に興味があります。手元の環境で試してみましょう。
今、プレイグラウンドが動かないのですが、これができますよね。できます、できます。確かに。例えば、let _ = 何か
と書いた場合、すべての値にマッチすることになります。コメントでもらった通り、すべてのものに代入してしまうということです。SQLで言うと、DELETE FROM テーブル名
みたいなもので、全てを一気に削除してしまうイメージですね。これは恐怖の文法ですが、まあロールバックすればいいんですけど、全滅させる文法という意味でワイルドカードのようなものです。
さて、これを実行させたいのですが、29行目が実行されたときの挙動がまたちょっと面白いです。パターンマッチングについての話です。プレイグラウンドが動いていないので多分動くことを前提に話を進めますが、評価式もパターンマッチングとして解釈されます。let
の後に何かを書いて、バリューバインディングパターンというものが後で出てきますが、これがワイルドカードパターンで代入されるという形になります。
ワイルドカードパターンへの代入は、その値に関与しないという感じなので、代入という価値観がないわけです。したがって、バリューバインディングをする必要がなく、_ = 何か
というふうに書くことも可能です。これは、例えば関数の戻り値を捨てたいときによく使われますね。let
を書かずに_ = 何か
とすることで、戻り値を無視することができます。
プレイグラウンドが動かないのですが、正しいので紹介程度に書いておきます。また動いてエラーが出たら補足しようと思います。こういった感じで代入式がパターンマッチとして解釈されるというのは、自分がこの資料を整理していて初めて気づきましたが、確かにそう動いていますね。
このあたりを意識すると、特にタプル的な表現を使った代入式がより幅広く使えるようになると感じました。
続いて、バリューバインディングパターンについて説明してみましょうか。バリューバインディングパターンは主にアイデンティファイアパターンと一緒に使われ、変数名や定数名に値を束縛(結合、代入)する動きを見せるパターンです。
定数名に束縛するときはlet
、変数名に束縛するときはvar
が使われます。var
を使う人は少ないですが、一般的にlet
を使って変数名に値を束縛するパターンも可能です。バリューバインディングパターンは、たとえばcase let 何々
のように書くことが多いですが、case var 何々
とも書けます。
これは元の変数に代入可能になるわけではなく、変数として取り出すのが容易になるという書き方ですね。この時に使われる識別子は、マッチした値を新たに格納するための新しい変数や定数として用意されます。
具体的な例を挙げると、スイッチ文でよく使われます。例えば、switch x
でcase let a
というふうに書いて、値が入っていた場合に何かをしたい時です。オプショナルパターンというのもありますが、一旦置いておきます。
switch x
でcase let a
と書いて、例えばイナミレーションパターンのようにcase .some(let a)
と書くと新しく確保されたa
を使ってprint(a)
とできます。これはバリューバインディングパターンとアイデンティファイアパターンを組み合わせた例です。var
も同じように使えるのでcase .some(var a)
と書けます。
プレイグラウンドが動かず説得力が欠けるのですが、もし間違っていたらOJTで補足します。警告が出ているのはa
を書き換えていないからですね。 a = 5
とか、まぁこんなコードを書いたら何の意味もなくなりますけど、これはミュータブルな値として取れてくるので、受け取った後に内部をちょっと調整して何かをしたいときに便利です。わざわざこれをlet
で受け取って、それを書き換えるためにvar
でシャドーイングしなくても大丈夫な仕組みになっています。
var
というものを使っているから、ミューティングな構造体を生かそうという発想につながってくる可能性がある表現方法かな、ちょっと遠回りな言い方をしましたけど、そのために構造体の扱い方が変わってくる可能性も秘めている表現かと思います。このvar
での束縛するというタイプね。
次にタプルパターンについて見ていきましょう。タプルパターンは非常にシンプルです。丸かっこで括って、カンマで区切った要素(0個以上)をリストアップするパターンになっています。これはタプルの値とマッチしますよ、というものです。
下の例ではlet (x, y) = (1, 2)
がイント型のタプル型で、それに対して同じ型の値を代入しています。この時の動きはバリューバインディングパターンになりますが、2つの変数がマッチしている感じです。ここで型を明記することによって、特定の種類のタプルにマッチするように制約をかけることもできます。これは例えば、代入式やあらかじめ明記されている型に渡すときなどに、型が違うよっていうエラーが出る場合のことを言っています。
例えば、let values: (Int, String) = (1, "Hello")
といった時に、両方ともInt
型のタプルはマッチしないよっていうことを言いたいのではないかと感じます。
これがスイッチ文ではおそらくできないでしょう。タプルパターンのケースを使っても、最初のワイルドカードパターンと同じく、1つ目はInt
型で、もう1つは何でもいいけどString
型ですよ、というのは書けなかったですよね。コンパイルエラーになってしまいます。これを書くためにはアノテーションではなくて、タイプキャスティングパターンを書く必要があります。is
ではなくてas
を使う必要がありますが、is
だと変数を受け取らないからではないでしょうか。
let
, or
, as Int
と書くときなど、少し特殊な状況です。is
だと変数を受け取んないからですね。
で、as
じゃないと代入できないですが、この表現、少しややこしいですね。_
を無視するのでis
を使いますが、普通のレッドだとas
になります。ああ、このノリで_
もas
なんだと思います。そして代入が受けたので納得です。
これで、is
を使うタイプキャスティングパターンもあるわけですが、ここではバリューバインディングパターンが使われています。普通はいけますが、プレイグラウンドでは少し特殊かもしれませんね。しかし、理屈的にはcase is String
書けますが、as
の場合は変数を使わなきゃいけない感じになります。
このようにいろんなパターンを一つ一つ押さえておくと、適切な場所で持っていける応用力が広がっていきます。今回、どんな場面でもマッチするパターン4つを紹介しました。次回は条件に応じてマッチしないこともある4つのパターンについて紹介したいと思います。
では、今日はこれでおしまいにしましょう。ありがとうございました。お疲れ様でした。