https://www.youtube.com/watch?v=2IiUGBUUF0A
今回は、前回から寄り道している Language Reference
の「Patterns」の続きを見ていきます。この前は どんな種類にもマッチするパターン
として4つのパターンを紹介し終えたつもりでしたけれど、最後の タプルパターン
が紹介しきれていなかったので、まずはそこから。
そしてそれに引き続きまして、後半の4つ 実行時にマッチしない可能性のあるパターン
について眺めていこうと思ってます。どうぞよろしくお願いしますね。
————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #36
00:00 開始 00:42 パターンマッチング 01:27 今回の展望 01:49 タプルパターンの利用制限 03:37 for-in や宣言で使うとき 07:01 for case let 11:34 for case によるパターンマッチング 13:53 単一要素のタプルパターン 15:41 列挙子パターン 16:27 case ラベルと case 条件 17:03 関連値と列挙子パターン 22:32 関連値をひとつの変数で受けたときの動作 24:20 タプルスプラット 29:30 タプルスプラットが使える場面 32:19 タプルで first が使えた気がしたのは単なる勘違い 32:49 列挙子パターンのおさらい 33:20 質疑応答 37:00 オプショナルな値に対する列挙子パターン 42:31 オプショナルを二重にしたときの列挙子パターン 47:26 それ以外を意味する列挙子を持たせる必要がなくなる 48:34 Swift の NULL ポインター表現 50:35 次回の展望 —————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #36
では、話を進めていきます。
今回は、前回お話ししたタプルパターンの続きからです。前回はスイッチで使われるパターンマッチングについて、8つのパターンがあり、それを大きく2つに分類できるとお話ししました。まず、どんな値にも実行時にマッチするパターンと、実行時にその値によってマッチしないパターンが4つずつあるということでした。その中で、どんな値にもマッチする4つのパターンについて説明しました。
今回は、状況に応じてマッチしない可能性があるパターンについて説明しようと思いましたが、まずタプルパターンについてまだ少し説明しきれていない部分があるのでそこから始めます。
タプルパターンというのはタプル的な表現のパターンで、他のタプルの値と構造的にマッチするかどうかを示すパターンです。このパターンには制限があるというお話がありました。大きく分けるとパターンマッチにはスイッチ文のケースラベルとして使う方法と、if文やguard文、for文といった条件として使う方法があります。特にfor-in文の中では、使えるパターンが4つに絞られているということが書いてあり、自分としてはあまり認識していなかったので気にしてみるといいかなと思いました。
具体的には、ワイルドカードパターン、識別子パターン、オプショナルパターン、そしてこれらを使って構成されているタプルパターンがあります。for-in文で使えるパターンはこれらだけです。この制限について意識してみるといくつかのパターンが実際には使えないことが分かります。
例えば、エクスプレッションパターンやタイプキャスティングパターンも使えないということが示されています。これらについて、プレイグラウンドで実際に試してみます。
let values = [(1, 2), (3, nil), (4, 5)]
for case let (x, y?) in values {
print("x: \\(x), y: \\(y)")
}
この例では、タプルの中のオプショナル値がnilでない場合のみマッチするパターンです。ここで使っているのはタプルパターンと識別子パターンです。他の使えるものとして試してみると、実際にオプショナルパターンが使えないことが分かります。
以下のようにfor-in文に対してオプショナルパターンが使えないのか確認しましょう。
for (let x, let y?) in values {
print("x: \\(x), y: \\(y)")
}
結果として、上記はエラーになります。他のケースでもfor-in文内でバリューバインディングパターンなどが使えないことが確認できます。
補足として、if文やガード文などでは使えるパターンもあることがわかります。
if case let (x, y?) = (1, nil) {
print("x: \\(x), y: \\(y)")
}
これをまとめると、for-in文内でオプショナルパターンやバリューバインディングパターンを使うことについての制限があるということです。ここは意識してコードを書く必要があります。
もし、不明点や追加の質問があれば教えてください。これで、今回の内容として一度区切りたいと思います。 なので、鵜呑みにすると間違った知識を唱えてしまうことになるので、公式ドキュメントをちゃんと確認することが大切です。ただ、このスライドは二次ドキュメントとして提供されていますが、一次ドキュメントとそんなに違わないと思うので、参考にしてみてください。しかし、一次ドキュメントだからといって必ずしも正しいとは限らないことがあるので注意が必要です。さて、次に進みましょう。
次に単一要素のタプルパターンについてですが、この辺りは仕様変更があったので印象に残っている方も多いかと思います。要素が1個のタプルというのは、変数そのものという扱いになるのです。つまり、値が1個のタプルと値が一致するということです。特性も値も全て一致します。具体的には、タプル型で作られているので別のものと見えるかもしれませんが、実際にはInt
を持つタプル型とInt
型は同じものとして扱われるという話です。
次に、値の状況に応じてパターンが一致しない可能性もある4つのパターンについて見ていきます。まずはエナメレーションケースパターンです。これはスイッチ文などで使用されることが多く、既存の列挙型の列挙子にマッチするパターンです。スイッチのケースラベルや、if
やwhile
、guard
の条件文でも使用可能です。
列挙型は関連型を持つことができ、値を自由に割り当てることができます。その場合、関連する値ごとに1つの要素を含むタプルパターンの指定が必要ですが、省略することも可能です。例えば、以下のようなシンプルな列挙型の定義があるとします。
enum AccessError {
case IOError(String)
case Unexpected
}
これをスイッチ文で使用する場合のコードは以下のようになります。
let error: AccessError = .IOError("File not found")
switch error {
case .IOError(let message):
print("IO Error: \\(message)")
case .Unexpected:
print("Unexpected Error")
}
列挙型の列挙子とマッチさせる際に、関連値が付属している場合はバリューバインディングパターンやアイデンティファイアーパターンを使って、以下のように書きます。
case .IOError(let message):
print(message)
しかし、値を問わない場合はワイルドカードパターン(_
)を使用して値を無視することもできます。もちろん、Swiftの場合、付属値のパターンを省略することも可能です。
以上がエナメレーションケースパターンの基本的な使い方です。プラットフォームによって挙動が異なる場合は、環境を見直したりする必要があるかもしれませんが、実際に手を動かして試してみると具体的なイメージが掴めるかと思います。 こんなふうに、普通の列挙子のように指定して丸と全部を指定するということができるようになっている列挙子のパターン、エナメレーションケースパターンはこんな感じですね。要素が二つある場合には必ずその要素を複数、ちゃんと一つ一つをパターン指定してあげないといけないというルールもあったりします。
もちろん、付属値に対するパターンを指定しない省略するパターンの場合はこれで実行できるんですけど、パターンを指定するときには、例えば x
みたいに一個だけで受けることができない・・・できた、できましたね。間違ったこと言いました。タプルで取れてますね。これですね、取れてる取れてる。そうか、できるんですね。この辺りはよく見るんだっけ、ジェネリックスとかでも見るし、クロージャーで見ますね。クロージャーのパラメータを取るときに見ますね。
これで、x
の例えば0番目の要素や1番目の要素みたいに取ることができます。1個1個取る必要はないんですね。これがよく見られるパターンとして挙げられるのは、さっきのバリューズ使うとわかりやすいのかな。今、ワーニングって言ってましたが、無視しちゃったね。デプリケーテッドになってた。ありがとうございます。なんかぼーっとしてた。デプリケーテッドだからやらないほうがいいんですね。昔できたっていうことかもしれないですね。
なるほど、ありがとうございます。ちなみにこういうふうにタプル、もしくは引数リストとか、今回もそれに近い形になるんですけど、こうやって複数のリストを1個の変数にまとめて取るという形、今回の例だとこの string
と int
のタプル、これをそれぞれではなくて、変数1個に取るやり方のことをタプルスプラットって言います。このタプルスプラットっていうのは Swift 1、2ぐらいまでは確か積極的に採用していたやり方なんですけど、Swift 3 くらいからタプルスプラットをやめようっていう動きに変わって、廃止されていったという経緯があります。
なので、この場面におけるタプルスプラットもそういう事情でデプリケーテッドになっているのかなという印象ですね。タプルスプラットの話はせっかくだからしておきましょうか。
タプルスプラットっていうのはよく使われる、というか要はあらかじめ引数としてこうやってタプルとして用意しておいて、それを例えば何かファンクションがあってレスポンス、これで例えばコードとメッセージみたいなのがあったときに、この関数に対していきなりこの2つの要素を渡すのではなくて、こうやってタプルをもうはめ込んでしまえみたいな、こういった動きを期待するの、これをパスするのを期待するのがタプルスプラットになります。
これが関数型言語とかだと積極的に活用されるのかな。ちょっとその辺りの世界分からないんで違うかもしれないんですけど、とりあえず好まれて使うということが意外とあったりします。だけど Swift ではこれを許すことによって、型推論とかオーバーローディングとかいろんな面で複雑さが増してしまうんで、これは廃止しようっていう動きになりました。
要は今回の sendResponse
、24行目みたいな書き方がタプルスプラットが許されていたら書けるわけですけど、これと sendResponse
、ラベルは無視してもらうとして便利上の説明なので、これとが同じわけですよ、インターフェース的に。ラベル無視しようって慣れてる人は気持ち悪いかもしれないからこうしようか。この2つのインターフェースが同じなんですよ。こうしたときに型推論するときにどっちを当てはめていくか、多分ピッタリ当てはまる方、20行目を採用すると思うんですけど、そうじゃなかったらタプルスプラットにするかとか、またさらにこれが型推論みたいな状況になったとき、いろいろと難しいことになってくる。
さらにこれで、any
型とかみたいなのも取っちゃったりするとね、APIデザインガイドラインの一番最後で紹介したみたいな状況に応じてどれが呼ばれるか分かりにくくなってくるとか、そういった制約が緩いだ故にいろいろと問題が起こってくる、みたいないろんな複雑な問題があるので、タプルスプラットは原則無しにしようっていう動きを見せています。
ただし一部では使えるところがあるんですよ。例えば args
がもうちょっと複雑にこうやって、こういうふうにタプルを配列にまとめるといった場合などです。 このあたり、もう少し整理しましょうか。この節では「タプルスプラット」について説明しています。
まず「タプルスプラット」とは、タプルの要素を個々の引数として関数に渡すことを指します。例えば、関数があり、エンドレスポンス(終了しないレスポンス)に対してargs.first
を渡すことは、タプルスプラットの制約上できません。これは引数の数が間違っているというエラーになります。しかし、args
にmap
を使うと、エンドレスポンスが書けるようになります。ただ、ここでエラーが発生してしまうので、調整が必要です。
それでは、具体的な例を挙げてみます。
let tuple: (Int, Int) = (1, 2)
上記のようなタプルがあるとして、この値を個別の引数として関数に渡したい場合にタプルスプラットが使われます。しかし、この機能は一部廃止されています。
ジェネリックパラメーターが利用されている場合に限り、タプルスプラットが機能することがあります。この場合、タプルの値をそのまま引数に当てはめる処理が行えます。この仕様は他の言語にも見られるもので、JavaScriptやPHPなどでも類似の機能が存在します。
例えば以下のようにタプルの要素を個々の引数として渡すことができます。
let function: (Int, Int) -> Void = { x, y in
print("x: \\(x), y: \\(y)")
}
// タプルスプラットではない
function(tuple.0, tuple.1)
ここでタプルスプラットを使わずに手動で引数を分けています。
タプルスプラットは特に、ジェネリックを使用する場合やコールバック関数を定義する際に便利です。しかし、現在のSwiftのバージョンではタプルのスプラットの機能が制限されているため、注意が必要です。
他の言語でもタプルスプラットに似た機能がありますが、その使用方法や制約は言語によって異なります。例えば、JavaScriptではスプレッド構文を使って次のようにします。
let tuple = [1, 2];
function func(x, y) {
console.log(`x: ${x}, y: ${y}`);
}
// スプレッド構文を使用
func(...tuple);
このように、Swiftに限らず他言語でもタプルの要素を分解して引数として渡す方法があります。
最後に、タプルスプラットの面白い例として、ダイナミックコール可能な関数を紹介しました。これは、関数の引数としてタプル要素を利用するものですが、現在のところはあまり使われないかもしれません。
以上が、タプルスプラットの説明でした。次に移りましょう。オプショナルな値に対する列挙子パターンについての説明です。これはSwiftの新しい機能の一つで、使い方次第で便利なものとなります。次回はこれについて詳しく見ていきましょう。 せっかくなので、これも見ておきましょう。簡単に言うと、オプショナル型は列挙型で定義されていて、.some
(付属値付き)と.none
の2つのケースが用意されています。オプショナルパターンを使う際には、一般的に.some
と.none
で比較することになります。
Swiftではオプショナル型に対して特別な配慮がされており、.some
を使った場合の列挙型も並列に指定できるようになっています。具体的なコードを見てみましょう。
例えば、列挙型があって、列挙型の名前をDirection
としましょう。Direction
は方向を表します。例えば、以下のように2つのケースを持つ列挙型を定義できます。
enum Direction {
case previous
case next
}
このように定義された列挙型を通常のスイッチ文で処理する場合、次のように書けます。
let direction: Direction = .next
switch direction {
case .previous:
print("Previous")
case .next:
print("Next")
}
これをオプショナル型にした場合、たとえば方向が特に指定されないことがある場合、以下のように定義します。
let direction: Direction? = nil
一般的な書き方だと、以下のようになります。
switch direction {
case .some(.previous):
print("Previous")
case .some(.next):
print("Next")
case .none:
print("None")
}
この書き方は少し複雑で、オプショナル型に不慣れな方には理解しづらいかもしれません。ですが、Swiftではこれを簡略化できるようにしてあります。オプショナル型については、some
がなくても値ありと値なしを同列に扱うことができます。
switch direction {
case .previous:
print("Previous")
case .next:
print("Next")
case .none:
print("None")
}
これにより、オプショナルではない列挙型Direction
の場合は.previous
か.next
の2通り、オプショナルのDirection?
になると.previous
か.next
かnil
という3通りの解釈になります。オプショナルの特性上、この3通りを理屈どおりに書けるようになっています。これはとても直感的で面白いところです。
オプショナルをさらにネストするとどうなるでしょうか?例えば以下のような場合です。
let direction: Direction?? = .some(.some(.previous))
この場合でも、ネストした.some
を書かないといけない場合があります。
switch direction {
case .some(.some(.previous)):
print("Previous")
case .some(.some(.next)):
print("Next")
case .some(.none), .none:
print("None")
}
さすがにこのような深いネストでは.some
を書かないと混乱するので、省略せずに書く必要があります。最終的に、オプショナル型の仕様に関しては、ケースごとの扱い方が直感的に書けるようになっていることが重要です。
インターフェースを揃えたい場合、場合によってはデフォルトケースを使うことは避け、ネストを深くすることも検討します。例えば以下のような方法です。
switch direction {
case .some(.previous):
print("Previous")
case .some(.next):
print("Next")
case .none:
print("None")
}
このように、オプショナル型により直感的で明確なコードを書くことができます。 スイッチをネストさせる方法が良さそうですね。ただ、完全にスイッチでその値があるときだけ何かしたいときには、オプショナルが重なっても対応できるので、便利です。そういった状況はあまり出ないと思いますが、なるほど、面白い発想ですね。やってみましょう。ケースも書けるはずです。
「nil」ですね。皆さん、素晴らしい発想ですね。nilでいくと、おお、行けますね。なるほど、こちらのほうが良さそうです。ディフォルトよりも少し良いですね。ちょっと気持ち悪いけど、頑張っていますね。スイッチとね。
このようにオプショナルパターンが展開される動きも、オプショナルが二重になるとややこしいですが、最初に見ていたオプショナルが1つだけのパターンの場合は非常に有益だと感じられました。
この面白いオプショナルの性格が、このオプショナルとそれ以外の型の融合っていうのがスイッチと上手だなと思います。この列挙子もそうです。例えば、ケースprevious
、ケースnext
でそれ以外何も指定しないときにケースneutral
と書いたりする一般的な書き方もありますが、この状態をわざわざケースとして明記せずにオプショナルで表現できることで、列挙型はバシッとprevious
とnext
で固定しつつも、あとで何もない状況を追加できるというイメージになります。列挙型がそのまま3つの状態を持つような感覚がとても面白いです。
さらに、ポインターでもその辺が上手に作られています。例えば、ポインターとしてUnsafeMutablePointer<Int>.allocate
でメモリ確保するとしましょう。このとき、変数p
の型はUnsafeMutablePointer<Int>
ですね。Swiftで面白いのは、このUnsafeMutablePointer
のとき、ヌルポインターが許容されていません。ポインターと言えばゼロも入るというのが常識かもしれませんが、Swiftの場合はゼロが入らない。ヌルポインターの概念を表現したいときにはオプショナル型として使うのです。そうすると、ヌルポインターという概念が急に取り入れられ、かつSwiftのオプショナル支援機能によって安全にヌルポインターかどうかを判定できるようになります。この辺りが非常に面白いですね。Swiftの素晴らしい点だと感じます。このように、オプショナルの活用は非常に興味深いです。
では、今日はここまでにします。次回は残りの3つのパターンを見ていこうと思います。お疲れ様でした。ありがとうございました。