https://www.youtube.com/watch?v=J1tciYA4u_Y
今回は A Swift Tour
では「制御構文」として扱われている「nil 結合演算子」から眺めていきます。その特徴的なところをおさらいしつつ、それをみ終えたら Swift の見どころのひとつとも言える switch
文について見ていけたらいいなと思ってます。よろしくお願いしますね。
———————————————————————————— 熊谷さんのやさしい Swift 勉強会 #33
00:00 開始 00:17 制御構文 00:39 オプショナル型 00:57 オプショナルバインディング 04:45 guard let 05:43 guard におけるシャドーイング 09:09 while let 10:48 switch とパターンマッチング 13:13 オプショナルパターン 15:25 while let で nil が現れたとき 17:02 nil を含む配列を for で繰り返すとき 17:16 for in where 19:44 for case 20:58 イテレーターを使う方法 22:21 練習問題 26:15 nil 結合演算子 30:48 条件演算子 32:52 nil 結合演算子 34:47 論理演算の応用技と nil 結合演算子の比較 42:03 || 演算子の独自定義を試みてみる 43:31 @autoclosure 46:30 nil 結合演算子が上手に作られている印象 47:16 クロージング 47:40 PHP 50:33 次回の展望 ————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #33
はい、じゃあ今日はコントロールフローの話をしていますけど、その中で制御構文というと、だいたいイメージするのって if
文とか while
文とか、そういったのをイメージするかと思います。前回もちょこっと出てきましたけど、オプショナル系も制御構文として紹介されているので、そのお話になります。今回はおそらくオプショナル系の話が主になってくる気がします。でも、制御構文という観点で見たお話なので、そんな前提で聞いていくと馴染みやすいかなと思います。
それで、まずオプショナルバインディングですが、前回お話ししたことでもあり、この後の話にも通じてくるところなので、この辺りをおさらいしつつ話を進めていこうと思います。オプショナルバインディングというのは、if
文と組み合わせて、値がないという場合にも対応可能な制御構文になります。
まずはそこまでかな。そうですね、その説明の2行目は単純な話で、変数名にハテナをつけるとオプショナル型になるというお話ですね。この中で大事になってくるのは、下から3行目の if let name = optionalName
というところです。オプショナルバインディングを使って制御構文を行っているというところになるので、この辺りをとりあえず軽く見ておきましょうか。
プレイグラウンドで、前回にもやったことですが、スライドを踏まえていくと、例えば何らかの値があって、普通に Int
型みたいにすると普通の Int
型しか入らない型になりますけど、これにハテナをつけることで nil
も入るオプショナル型になりますという感じですね。この中に値を何回入れてあげて、それで if let value = value
とすると、右辺がこの上のところで定義した変数で、let
の後の value
、これがこの if
分の中で使えるスコープになっている変数です。そして今回大事なポイントとして、この if let value = value
ここの部分がオプショナルバインディングという構文になっているわけです。
オプショナルバインディングの大事なポイントとしては、右辺は必ずオプショナル型にしないといけません。そして let
の後にそれを受ける変数名を添えて、右辺に書いた値が nil
じゃなかったときにその変数に値を割り当ててバインディングして設定します。これによってこの if
文の中で使えるようになるというのがオプショナルバインディングの大事なポイントです。
もし値が入っていなかった場合には、こちらの else
ブロックが呼ばれるようになっていて、例えば値が nil
だったとすると else
ブロックのほうが実行されるという、こういう制御が行えるというのがオプショナルバインディングです。必ず if
と let
で使う構文かなって言っちゃって大丈夫ですかね。ガードもありますね。
ガードの場合は guard let value = value else {}
で、何か print
するなり、else
ブロックのほうに処理を持っていって、ガード文の特徴として終了させないといけないので、今回は fatalError
で終わらせておきましょうかね。グローバルスコープなので。
それで、条件が成立したときの場合の変数をこちらに書く。このときの value
っていうのがガードのところで使ったオプショナルバインディングの value
になるけど、これは構文エラーになっているかな。ちゃんと動くかな。エラーになるよね。エラーになっちゃうか、fatalError
か。あれ、ちゃんと動くんだっけな。
value
が重なっていそうだけど、シャドウイングするんだっけ。ちゃんとするのか。なんか覚えてないな。value
がシャドウイングされてるね。うん、大丈夫。そうか、大丈夫だっけ。なんかダメなパターンがあったような気がするんですけど。昔書けなかった時あった気がするんですよね。え、書けなかったよね。1行目で value
やってるので、3行目のときにクロージャーとかの変数とか引数とかはいいんですけど、ローカル変数かぶってるっていうのが出た気がするんですけど、なんか最近あれ、書けんだって思った記憶が。
あ、本当。そうそうダメでしたよね。それはチュートリアルパターンですよ。ダメだと思った。いやめんどくせーと思った。そのパターンめんどくせーだったですよね。プレイグラウンド独特とかもあるようだからちょっとこうやってみる。いやできるようになった気がしますね。なんか最近書いた思いも。あ、本当。いやあわかんない、なんか書けるなっていう。でもできてますね。
あーいいですね。だいたい関数に閉じればプレイグラウンド独特な動きってね、ある程度抑えられるんですけど。はい、シャドウイングするようになったので。いやこれはいいですね。いいですね。いいいい。まあまあまあまあいい。はい。混乱しかねない気もしなくもないけど。関数はパラメーターでもあり得たので。そうですね、まあいいかなっていう感じですね。
うんうん。はい。いいですね。 結局ね、うんうん、いいですね。そうそう、こっちだとそもそもスコープのレベルが1個下になるから、まあシャドウイングするよねって感じでわかりやすいんですけどね。うん、こっちは動くでしょ? 動くよね、ちゃんとね。まああっちも動いたけど、うん、そうなんだ。まだ慣れないから気持ち悪いですけど書けるんですね。よかった。
なんか、書けなかったときは書けなかったときでね、なんとなく微妙だなって思ってたけど、いざ書けると気持ち悪い、この面白い感覚。なるほど、ちゃんとシャドウイングをする。で、ちなみにね、シャドウイングってしてしまうと、スコープ内でアクセスできない限りはもう元の変数はいじれなくなるで、ガードを通しちゃうともうそのオプショナル型の値のインフとバリューにはアクセスできないので、安心して nil
の心配なく処理できるよっていう状況になります。うん、これいいですね、面白いです。
ちょっと慣れていけばいい感じになりそうですね。えーと、これぐらいかな。だからオプショナルバインディングっていうとこんな感じで。
で、で、で、で、で、で、で、で、で、で、漸く漸く y2 がとれて、あーそっか。これはオプショナルパターンじゃなくオプショナル、オプショナルパターンは違ったね。そうですね、はいこうでした。いつもこの感じ。この書き方、嫌う人多いですよね。いや、ここだけじゃないですか。オプショナルになる体験は同じようなイコールとかモンティングかけないんですか? なんかもうちょっと同じような感じにしていきたい。オプショナルパターンだとこうなってね、こういう書き方に変わるんです。dot some
とかでも、まあ dot some
でも、この時はね。また面白くて、大事なポイントとして、これがオプショナルパターン、ここまで行っちゃっていいかな。とりあえずこれがオプショナルパターンで、これがバリューバインディングパターンで、そうですね、そうやって構成されていて、ここでね dot some
ってやると、これがエミュレーションパターンで、これでバリューバインディングパターンってね、違うパターンの組み合わせに変わる。で、下はオプショナルパターンでしょ。上と同じようにエミュレーションパターンにしたい場合にはここがないですね。こういうふうな書き方。混ぜられるんで、別に統一させなくていいですけどね。オプショナルパターン、確かにね、自分はなんかね慣れてしまってこれ好きなんですけど、気持ち悪がられますね。これですね、こればっかりある視聴者殺しですね。そうだね。そうそう、2つの値がパープルにして、両方が nil
がある可能性、そういう場合に好んで使いますね。イメージとしては、覚えればいいかなって感じ。
そうですよね。この「覚えれば」がね、なかなか親しみがない書き方、高いですよね。そういう人、覚えること多いから。そうですね。なんだろうね、このオプショナルパターンは特にね。今までの “?” って nil
が取れるよとか nil
ですかって問い合わせてる感じがするのに、パターンマッチングの時に nil
じゃないのが取れてくるのがね、最初すごく気持ち悪くて、そう、なんかね、大変でしたけどまあまあ慣れればと見やすい。
で、余談に飛んでいきますけど、バリューバインディングパターンを使わなくていい時には、こうやってね、アンダースコアってやると、値がある時にここにマッチするっていうね、さらに気持ち悪い書き方になっていく。まあ慣れてればわかるんですけど、何やってるかわかんないですよね、最初のうちね。アンダースコア “?” とか言われてもね。今 nil
になっちゃってるのか、こう飛ばせばちゃんと print some
が出るはずなんですよね。引っ越されてないか、こう待ってる間にちょっとコメントを読む。
ファイルのサンプル構造って、配列の中に nil
がある場合はスキップってどうなりますか? ファイルの場合はね、中断になります。スキップではないですよね。スキップではない、抜けちゃう条件がある。これね、新機器でファイルを回してる時はね、false
になった時に抜けるって自然にわかると思うんですよ。false
になった時にスキップしないですよね。っていうのがね、そんな感覚でね、イメージすると、オプショナルバインディングが成立しなかった時点で終わる。成立している間回るっていう捉え方で混乱しにくくなるんじゃないかなと思います。と、補足できるのは自分もよく混乱してたからなんですけど。
あともう一個ね、混乱するパターンを紹介したい。フォーループの場合はスキップします。 「あ、でもね、混乱してるね。自分もね、まだわかりきってないや。ちょっとやってみましょう。
フォーループの場合にnilが途中にあったとき、多分スキップするよね?するよねとか、確証が意外と混乱しません?普通にnilが入ってるだけ。オプショナルバインディング。あれ、そんなに書けないんでしたっけ?書けないんだっけ?書いたことないですよ。レア?レアとかはできますけど、こういう風になっちゃうんだ。そうですね、こういう風になっちゃうから書けないんだ。これなら混乱しない、これならOK。
こうやって書くんですよね。要はね、書いたことないですけどね、これは使い勝手も悪いからちょっと書かないですけど、この場合はスキップします。ただ、インターのオプショナル型ってよね。そう、オプショナル型。だからあんまり使わないんですよ、この書き方は。
で、もう1個思い出した。オプショナルで出てるでしょ。で、これで5の後にnil飛んで7に行ってるから、4の場合はちゃんとforEach
的なので、すべての要素に対してループ回して、で、where
がついてるときにはその条件を満たしたものに限ってブロック内に入って、そうでなければ次へ行くよっていう感じになるので、これをね、明示的なコードとして書く場合にはif value != nil
だった場合はって書くけど、ガードにしよう、今回。ガードguard let value = value else { continue }
っていうね、こういう雰囲気になります。ブレイクじゃなくてね、continue
。
まあなんか書いたほうが混乱したかもしれないけど、まあまあいいですね。で、この時はね、while
いらないです。これと同等動き、ちゃんと動くよねきっとね。そうそう、1から6というか6個目を飛ばして7。こういう風な、ね、for
の場合はこういう風に各要素に対して回す。そしてwhile
の場合は条件一致しなかった時に抜ける。で、もう1個、for case let value
はどうなの?多分continue
するよね。動くよね。これは自分の勘違いじゃない。動くよね。6を飛ばして7。しかもオプショナルが解けてるっていうね。こっちを書きますね。普通はね。面白いよね。
これはfor
の後にパターンマッチングを使って、value binding
パターンとオプショナルパターンを使った繰り返し処理になります。for
ループ強いですよね。これでさらに、ね、value
が3よりも大きいものみたいに回すこともできて、こうすると、ね、4, 5, 7になったりとか。可読性もなかなかいいと思うんですよ。個人的にこういう書き方は結構好きで、使ってますけど。
さあ、ここでコメントに何かが寄せられている。チェックしないといけない。最後まで回す方法、iterator.next
, うーん、なるほど。iterator.next
、はいはいはい、なるほどね。二重のね、オプショナルで捉えているのをバインディングして、ヒントのオプショナル型で取ってるはず、多分ね、うん。そうね、for in
構文を展開したやつまさにその通りですね。うん、なるほど。いろいろありますね。はい。だいたいしゃべり尽くしたかな。
とりあえず今はオプショナルバインディングの話をしておりまして、この今書いたフォーループとかは全然オプショナルバインディングではなく、ここですね、今の場合、guard let value = value else { continue }
、ここがオプショナルバインディングになります。
はい、じゃあ次行こうかな。はい、まあね、こういう風な、ね、オプショナルバインディングがあるわけですけれど、はい、この練習問題を、とりあえずやっておきたくて。で、ちょっとオプショナルバインディングの復習をしたんですけど、先ほどの例で、まあ前回の例でもいいかな。オプショナルネームをnilにするとどんな挨拶文がこのコードでは得られるでしょう。またその時に挨拶文、別の挨拶文を設定するelse
節を追加してみましょう、っていうのをとりあえずやってみますかね。
なんかたくさん話したから、やんなくても大丈夫な気がしてきた。まあいいや、とりあえずね。同じ挨拶文とか、そういう話はとりあえず抜きにして、リフレットでね、処理をして、この時にvalue
がなんかね、値入ってるよっていうのをね、表示するっていう例ですよね。とりあえずね。
で、アクション関数ももういいかな。えーと、これでいいかな。はい、で、ひっこさせた時に、value
として1位が出るんですよ。ね、出ますよね。ちょっと分かりにくいが、分かりにくいというか、ここがちゃんと動いてるよっていうのを表示させて、出ますよね。で、これをnilにした時にはどうなりますか。はい、分かる人っていって、まあおおよその人が分かるかなと思うんですけど、オプショナルバインディングの性格としてね、右辺がnilだったら何にも値が得られないので、value
バインディングパターンが成立しない。つまり、イメージ的にはフォルス的なイメージになるみたいなふうに捉えると、分かってきて、文字越されてるのかな。何もないですよね。」 では、これで else
文を追加して何かしてみましょう、というお話ですね。例えばこうやって書くと、Nilである場合は else
のほうに行って、そっちの文字が出ますよという動きになります。これで、オッケーですね。
仮にオプショナルバインディングをしなかったとすると、そのままオプショナルの値を取るとき、こうやって書くとオプショナルな値になります。今は nil
と出ていますが、値があればその値が出て、なければ nil
が出ます。
こういうふうに、値がある場合とない場合があります。ここで先ほどの if
文のように、値があったときにはどういう処理をするか、なかったときにはどういう処理をするかということが可能です。その時に使える方法として、nil
結合演算子があります。
これはどういうものかというと、オプショナルな値の後ろに ??
を付けて、値がなかった時の代わりの値を指定します。例えば、値がなかったときには -1
扱いでいい場合、let value = optionalValue ?? -1
のように書いてあげると、nil
の代わりに -1
になります。nil
結合演算子の大事なポイントとしては、右辺に指定する型は左辺のオプショナル型にラップされている型、今回の場合は int
と同じ型を指定しなければなりません。
オプショナルも指定可能です。他にもいろいろなことができます。オプショナル値を連結して nil
でなければ指定した値にするといった処理もできます。ただし、一般的にはラップされている型と同じ型を指定するのがオーソドックスな形です。
例えば、先ほどの if
文で作っていたようなものと同じコードを作りたい場合、文字列値がちゃんとあればそれに対して絵文字付きの文字列にし、それ以外ならばデフォルトの値にする、といった処理を行いたいときにはオプショナルの map
を使います。
let optionalValue: String? = ...
let result = optionalValue.map { "😊" + $0 } ?? "😐"
このコードではオプショナル値があればその値に絵文字を付け、そうでなければデフォルト値を指定しています。基本的に何らかの評価式を評価し、それが nil
でなければその値を使用し、nil
であれば右側の値を使用するという制御構文となります。
他にも、似たような制御構文として、3項演算子(?:
)がありますが、Swiftではあまり使われません。これはオプショナルの比較と強制アンラップを行うため、あまりスマートではないからです。
他にSwiftに特有のオプショナルの扱いとして、オプショナル型の map
といった方法があります。時々便利です。
さて、ここまでが大体の説明です。もう少しシンプルな例で、 nil
結合演算子のお話をしましょう。例えば、
let x: Int? = ...
let y = x ?? 0
のように書くと、 x
が nil
であれば 0
になります。
では、スライドに戻って、ここまで何か質問がありますか?コメント的には大丈夫ですかね。他にも面白い書き方がありそうですが、一旦ここまでにします。今紹介したのが、 ??
演算子でデフォルト値を提供できる仕組みです。 値がないとき、つまり nil
のときですね。その際に大事なポイントとして、さっきの例で紹介したように、今回の例では String
型で結果が得られるわけです。String
のオプショナル型ではなくて、String
型として得られるというのが正確なところです。
さらにデフォルト値がオプショナルだった場合には、そのままフラットな感じで String
のオプショナル型として得られます。それでOKでもいいし、さらにデフォルトを ??
で追加してもいいです。いずれにしても、着地点は左辺の型、つまりオプショナル型、またはそれをアンラップした型のどちらかに集束していくという仕様です。
ではここから、これがよく作られている部分だと思いますので、Playgroundで紹介します。この書き方、例えばよくある言語だとこうです。これすごく参考になると思います。左のステートメントを実行して、結果が得られなかったときには右辺のステートメントを実行し、結果を得るという動きをするんです。これに慣れている人、特にスクリプト言語とか、C言語、Objective-Cもそうです。こういう書き方をするんですね。あれ、Objective-Cどうだったかな?するね、するする。確か。忘れちゃった。でもいいや。こんな書き方をするというのがありましたけど、感覚を捉える上では7行目みたいなニュアンスで、6行目をやっているという風に捉えてもらえればOKかと思います。
改めてこの7行目って、結構奇跡的な構文になっていると思います。これが成り立つためには論理演算が必要です。論理演算で値そのものが真偽値として扱えるという文化が大前提なんです。Value
がゼロ相当の値じゃなかったときには、それを採用して Y
にしますよ、というね。そうじゃなかったときには、これは論理は、論理はなので、左辺と右辺を論理的に「和」を取るわけです。これでOR
になると言語によっては論理演算でちゃんとフォルスになっちゃうか、とか。確かこうだったような気がします。
そうすると右辺の処理が実行されて戻り値が特殊じゃないとダメなのか。なるほど。右辺の評価式が実行されて、ゼロ相当のものとゼロ相当以外のものが OR
で判定されると、基本的にはすべての情報を失うことなく演算が終わるはずです。たぶん。そうすると右辺の値になりますね。この評価式全体が。そしてこの時、ブール型に丸め込まれていなかったとすると、その値がそのまま Y
に代入されて、結果として右辺の値が使えるようになるという解釈で合ってるのかな。ちょっと自信がなくなるぐらい、結構複雑な処理をしているというのがここを読んでいくと見て取れるかなと思います。そういう感じです。
なので、値がゼロ相当か否かという文化と、論理演算という文化と、評価式の結果が値として得られる文化。言葉で言うとそのままだけど、評価式の結果が戻り値として取得できるJavaScriptとかでよく見られますけど、一番最後に実行したものの結果が戻り値みたいな文化の言語ってあったりします。そういった多くの文化が複合的に働いて、かつ、実行した結果が、コマンドの終了コードみたいな文化があって、必ず終了したときに評価できるものが得られる。何にも値を返さなくても、制御終了というような値が得られるみたいな文化とか。いろんな文化が積み重なって、OR
演算による値をまたはとして取るみたいなのが成立している感じがします。ちょっと説明がうまくできていないかもしれないですが、とても複雑なバランスで成り立っています。
Perl言語に至っては、OR
演算子としてではなく、アルファベット表記の or
っていう書き方があり、これは縦棒2本と全く同じ動きをするけれども、評価順位が最も低い演算です。シェルスクリプトもこの書き方です。Perl言語を知らない人にPerlを進めるのは難しいですが、この or
を使った書き方はとても好きですね。データライブみたいな、こういう書き方が英文として読めるのも面白いです。Perlではこんな書き方もできます、という紹介です。
条件処理の失敗時にフェイタルエラーを起こすという書き方がPerlでは可能ですが、Swiftでは基本的にできないです。でも、自分で定義してあげればできるんですよね。これはちょっと面白いですね。例えば、関数で static func
を定義して、左辺が void
、右辺が Never
などのフェイタルエラーを起こすような処理。これプリントにしてみようか。PHPでも同じようなことができます。PHPはPerl言語を参考に作られた言語なので、とても似た表現が出てきます。
以上で今回の内容を終わります。これからもSwiftを中心に、いろいろな言語や技術について学んでいきましょう。 「これじゃだめか。ここがそもそもSwift言語の形じゃないですね。では、調整を終了しちゃいましょうか。これがオートクロージャですね。オートクロージャってどちらに書くんだっけ?詳しく書いてないから忘れちゃいました。型のほうに書くんですね。皆さん、オートクロージャって使ってますか?
おだしょー:デバッグのときですかね。デバッグのときやアサート系では大事な気がしますね。
確かにそうですよね。デバッグのときには即時に評価させたくないときが確かに多いですね。でも、これだとフェイタルエラーが発生しますね。これは自分でちょっと研究してみようと思います。このフェイタルエラーが実行されるからじゃないですかね。
そうかそうか、読んじゃうからですね。これが成功したか、こんなにいけないか。LHS(左辺項)を何も引けないでいきなりフェイタルエラーを実行しちゃってるからですね。そうですね、LHSを評価しないといけないんですね。
つまり、LHSを一度評価しないとダメなんでしょう。評価してLHSをやる必要があるかどうかですね。それを判定しないといけないわけですよね。そうです、だからVoidのオプショナルとかを使ってなんとかしないといけないけど、オプショナルじゃだめですね。基本的にはできないかもしれないので、やめておきます。
あとでリフレッシュしてルーレッジを...ルーレッジとかなくなっちゃいますね。エラースローですね。エラースローといった失敗を受け取らないといけないじゃないですか。でもちょっと良い例になりました。ここでLHSをどう評価するかが、とても大事なポイントですね。このあたりが実際に成功したか失敗したかの情報を得られない限り、7行目のような言語仕様が作れないということです。まさにこの例でそれが出てきましたね。
結構良いバランスで7行目ができていますね。逆に言うと、6行目が非常に安全かつ正確に動くようにできているということです。なかなかうまく作られた演算子だなと、他の言語と比べても感じます。
この後、もうすぐスイッチ文に入りますので、それについては時間がかかりすぎるので、今日はここまでにしましょう。何か質問があれば、お話できる時間がありますが、何かありますか?
参加者:PHPについてですね。自分は最初にPerlを非常に好んで使っていたのですが、その後 PHPという言語が出てきて、やってみようと思ったんです。今のPHPって何の略なんですかね?
PHPはかつてPerlを元にした言語というふうにどこかで見た記憶がありましたが、実際に使ってみると全然違いました。なので、PHPに対して勝手な思い込みで憎しみを持ったことがあります。
でもエッセンスとしては似ている部分もあります。例えば、変数宣言には $
を使いますね。Perl言語は何でも省略して書ける言語で、受け先を指定しないと勝手に $_
に変数が入るんです。それに対してPHPも似たようなエッセンスがありますが、ローカルスコープの面ではちゃんとしている気がしますね。
特に質問がなければ、今日はこのへんで終わりにしましょう。次回また続けていきましょう。ありがとうございます。」