https://www.youtube.com/watch?v=52N2pwoU2SQ
今回からは A Swift Tour
の「制御構文」を眺めていきます。このセクションでは Swift にはどんな制御構文が用意されているのか、そしてそれらの使い方や特徴といったあたりを見ていけたらいいなと思ってます。よろしくお願いしますね。
—————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #32
00:00 開始 00:22 制御構文 01:38 基本事項 02:46 制御構文の括弧表記 11:55 条件処理 if の記法 13:00 サンプルコードはスリムに 14:29 条件式は真偽値で表現 17:38 ゼロかゼロ以外か 18:34 C 言語の真偽値は C99 から 20:55 等価比較演算子は関数として定義されている 22:21 条件式で真偽値以外を使える場合の難しさ 23:38 ヨーダ記法 25:59 if case 27:11 パターンマッチング 31:07 オプショナルバインディング 32:06 if 文で使えるさまざまな条件式 33:57 Kotlin でのオプショナルの扱い 37:02 Google App Script での型の扱い 39:21 arg1 と Xcode の補完機能 41:58 if 文の条件式に記載できるもの 42:29 オプショナルバインディング 44:06 オプショナルの変数名 49:05 クロージング ——————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #32
はい、じゃあ今日は新しいセクション、「コントロールフロー制御構文」ですね。これに入っていこうと思います。まず、基本的なところからしっかり押さえていこうかと思います。
コントロールフローとしてSwiftには、『The Swift Programming Language』には以下のものが挙げられていました。普段何気なくプログラミングしていると、制御構文って肌に馴染んでいるので、「これだけだったかな?」みたいな感じが個人的にはしちゃうんですけど、あえて追加するとしたら try do
構文とか do try catch
ぐらいかなと思います。それ以外は、まあこれぐらいでしょうね。
具体的には、if
、switch
、for in
、while
、repeat while
などです。多分他にはないはずです。あとは do
ですね。これについて詳しく見ていく回になります。
基本事項として、全体としてこれらの条件処理と繰り返し処理、コントロールフローが分類分けされていますが、どっちにも言えることとして、条件式や繰り返しで使う変数を作る際、丸括弧はなくても良い、とのことです。後でプレイグラウンドでいじってみましょうね。それと、条件処理や繰り返し処理の実装部分を作る並括弧は必須です。この2つがSwiftの大事なポイントになると思います。
大事なポイントと言うには大げさかもしれませんが、ここを意識することによって書きやすくなりますし、安全性が高まります。C言語と比べて、といった方がわかりやすいかもしれません。Swiftが誕生する上で重要なポイントです。
では、この辺りをちょっとプレイグラウンドでいじってみましょう。丸括弧が要らないところと、並括弧が必須なところですね。
例えば if
文は一番一般的な制御構文になりますが、これは古来からあるものです。昔のC言語の書き方だと、例えば条件式があって、何らかの真理値を返す関数を用意します。仮に計算型プロパティとして用意するとしますね。
if (condition) {
print("A")
print("B")
} else {
print("C")
}
こんな風な書き方をするのが一般的でした。この書き方自体は Swift でもできますが、より書きやすく、さらに安全にできるようになったのが Swift の新しいスタイルです。基本的には新しい書き方の方が向いているはずなので、その書き方をやっていくことになります。
プレイグラウンドがうまく動かないので口頭で続けますが、Swiftの場合、この if
文の丸括弧がいらなくなったのが一つの特徴です。
if condition {
print("A")
print("B")
} else {
print("C")
}
見やすさも向上しますし、わざわざ丸括弧を書く必要がないので非常にありがたいと感じます。
また、昔のC言語の書き方では、if (condition) print("something");
のように、ステートメントが一つだけであればブロックを定義しなくても書けていました。その書き方が可能でしたが、Swiftではこのような書き方はサポートされていません。Swiftでは必ず並括弧で囲む必要があります。
他の構文についても同様に見ていきますので、後ほどプレイグラウンドでの実践も交えつつ進めていきましょう。 これは Swift では廃止になっているというのも、C言語の時はね、こうやって行を変えることもできました。C言語には行末をセミコロンで書くという決まりがありましたが、こうやって書くことができたんです。でも、これがうっかりすると大変なことになります。
例えば、Swiftの構文では並括弧を書かなくてもいいという仕様があった時に、else
がない場合にうっかり並括弧いらないよねと思ってこれを消した途端に、C言語の仕様だった場合には、条件式が真だった場合に実行されるのはこの例では7行目までみたいになってしまいます。いくらインデントを揃えても、そうは行かないのです。
なので、8行目が条件式とは関係なく必ず実行されるという動きになってしまい、本来仮にこう書きたかった時に誤動作を引き起こすことになります。例えば、条件を満たしたらprint("A")
を表示し、その次の行では条件式を抜けてprint("B")
を表示するという動きになってしまいます。こういったコードのうっかりミスを防ぐ意味でも、この並括弧が必要なのはSwiftとしてとても大事なポイントになっています。
ただ個人的には、条件が1つだけの時に、そのステートメントをサクッと実行する機能が欲しいなと思うところです。例えば、if true { print("A") }
みたいな感じですね。あまり良い例ではないですが、このように書けたら便利だなと思います。
Perl言語などを使っている人は、print "A" if condition
のように、条件に応じて1ステートメントだけ実行したい時があるかもしれません。Perlでは、条件式を満たす場合にのみprint "A"
が実行されるような書き方ができます。個人的に、この書き方が好きなんですよね。
例えば、オプションを設定する時に、var options: String
を用意して、options += "query=" + query if !query.isEmpty
のように書きたいんです。Swiftの場合、if
ではなくwhere
でも良いのですが、何かしらの構文があると便利です。Swiftでは、where
がちょっと良いかもしれません。
Swiftでは、if
文には丸括弧がなくても良いですが、並括弧が必ず必要というのが大事な約束ごとです。while
文も同じように、必ずコードブロックを取るという仕様になっています。 はい、ここまでですかね。このページに書いてある内容について、実際にif文とかをゆっくり見ていきますね。
Swift の if文において大事なポイントとして、条件式は必ずブール型として評価される必要があります。これはやはり全体的にC言語と比べて意識されています。この話は何回もしてきた気がしますが、とにかくブール型として評価するのが大事です。C言語のように、暗黙的にブールとして評価される仕組みはないということです。
例えば、以下のようなコードがあります。
if score > 50 {
// 何か処理
}
当たり前のことを言っていると思う方も多いと思いますが、昔は違ったのです。特に5年くらい前までは、iOS界ではObjective-Cが主流で、とりあえず真実としてゼロ以外がtrue
、ゼロがfalse
とされていました。つまり、暗黙的にゼロ以外の値がtrue
と見なされていたわけです。
C言語も同じで、例えば以下のような書き方ができました。
int value = 10;
if (value) {
// 何か処理
}
この場合、value
がゼロでない限り条件はtrue
となり、処理が実行されます。同様のことが多くの言語で可能でした。Visual BasicやR、JavaScriptなども同じです。これは意外と常識だったとも言えます。
ただし、Swiftではこれができません。条件式には明示的にブール値を返す必要があります。たとえば、インスタンスがnil
であるかどうかをチェックする場合、以下のように書きます。
let obj: AnyObject? = nil
if obj != nil {
// 何か処理
}
これも明示的にnil
チェックをしています。同じように、ブール型の場合は以下のように書きます。
let isTrue = true
if isTrue {
// 何か処理
}
これが許されている範囲で、その式が必ずブール型を返すようにする必要があります。
C言語などの昔のスタイルの言語ではゼロかゼロ以外かで判断される場合がありますが、Swiftではその発想が異なります。Swiftは完全にブール型に依存し、その値がゼロ相当かどうかといった判定は行われません。プログラム的には、これは大きな違いです。
この違いを理解することが、Swiftのif文や条件判断を正しく使う上で非常に重要です。経験豊富なプログラマーでも、この点は注意が必要です。 今時の人は全然気にしなくても大丈夫です。ただ、余談として言っておくと、数字がゼロかどうかを判定する演算の定義を辿ると面白いです。今、この演算子もちゃんと関数として定義されていて、それによってブール値を返す動きになっているのが大事なところです。
例えば、Swiftでは==
演算子はブール値を返す関数として定義されていて、それによって18行目では==
演算子を評価し、その戻り値として得られた真偽値を使って条件処理を行います。この仕様によってバグが大幅に減ったというコメントもあり、実際にその効果は大きいです。
例えば、オプションの配列があったときに、なんでも許されちゃう状況がありました。もしゼロ相当だったらという条件が許されていたとしたら、次のような書き方ができてしまいます:
if options.append(0) {
// このコードがどう動くのかを理解するのは難しい
}
実際、このような書き方の意味をすぐに理解できる人は少ないでしょう。値を返すアペンドでもいろいろと問題が出てきます。しかし、コンパイルが通ってしまうため、後で困ったことになるかもしれません。
余談ですが、「ヨーダ記法」という書き方があり、それは定数を左に、変数を右に書くという方法です。例えば、if (0 == variable)
という風に比較します。これは、誤って=
を使ってしまった場合に代入しようとしていることに気づけるというメリットがあります。この書き方は一部の言語で推奨されていたこともあります。
例えば、CやObjective-Cでは以下のように書きます:
if (0 == variable) {
// comparison is obvious
}
これにより、誤って=
を使った代入ミスを防ぐことができます。Swiftのようなモダンな言語では、手続き的なエラーを防ぐための様々な工夫がされています。
それからもう一つの余談として、if case .A = value
という書き方があります。これは列挙型(enum)で使うパターンマッチングの一例です。例えば、次のようなコードです:
enum Value {
case A, B, C
}
let value = Value.A
if case .A = value {
// value が .A の場合の処理
}
これはif
文の中に条件式ではなくパターンマッチを書く構文であり、ブール値を返すのとは異なる動きになります。条件にマッチした場合には、その条件が満たされたとみなされ、真の条件ブロックが実行されます。
このように、Swiftには多様な書き方やエラーチェックの仕組みが備わっており、柔軟かつ安全にプログラムを書くことができます。 要はね、審議値を取るか、またはパターンを書いてパターンにマッチするものを満たすかっていう感じでしたね。ここも面白いところで、パターンマッチにはいろいろある中で、このエクスプレッションパターンになるのかな。これ全体を見ると、そうかな、ここがエクスプレッションパターンですね。
パターンマッチングにはいくつか種類があって、確か7つあるんですけど、そのうちのエクスプレッションパターンについては、評価式が true
を返した時にパターンがマッチするっていう仕様になっていて、このエクスプレッションパターンのための演算子が用意されています。それがチルダイコール(~=)
ですね。値が ~=.a
で true
返りますかね、返りますね。
エクスプレッションパターンに限ってはエクスプレッションパターン用の演算子を実行して、それが true
だった時にはそのパターンがマッチしたとみなされます。万が一、それしか書いていない場合は全体としてパターンにマッチし、故に if
の条件を満たして実行されるという、ちょっと複雑な動きをします。
どういった評価をした方がいいかなと考えた時、基本的には if
文です。if
文の中でパターンマッチングを使う時に case
文を使う感じですね。スイッチ文でもその中でパターンマッチングを使います。まあ、パターンマッチングしか使えないんですけどね、っていう書き方になります。ちなみに while
文でも確か case
が使えます。while case
。これ無限ループするからやめよう。確かに書ける。あんまり書く機会はないけど、いや、書く機会がないのは自分だけかもしれない。
スイッチ文もちゃんと説明してないから、これはスイッチ文のところでたっぷりお話ししますね。これでコンパイル通るかな、通りますね。ですから、スイッチ文、if
文、while
文、それぞれでパターンマッチングを使うのが自然だと思います。同様にオプショナルバインディング、if optional = options
みたいな書き方もありますよね。この if let
という構文も、if
文の中でオプショナルバインディングを使ってるという感覚になると思います。
これと比較すると同じ if
文で、オプショナルバインディングを使っているパターンと、パターンマッチングを使っているパターンという風に自然な解釈ができる気がします。多分こんな感じでいいと思います。こう書いていて思ったけれど、もう1つありましたね。オプショナルバインディングで値が取れた時に true
とみなされて実行されるというのもあります。なので、ブール型を取るというのが一つと、値があった時に実行したい時にはオプショナルバインディングを使うというパターンと、パターンマッチングを使うというこの3つが基本です。
いろいろ考えてみるべき点としては、昔のC言語の書き方を振り返ることもありますね。エクスプレッションパターンならこう書いていけば良いわけですが、なんとなく複雑な気がします。特に代入式では、変数そのものがすでに定義されている時には代入されるかどうかわからなくなってきます。そんな中でも、オプショナルバインディングとか、let
でやるのが大胆だけどわかりやすくて面白いですね。そして、1フレーズ添えることでコードが明瞭になる。
脱線話として、Kotlinの場合は if let
は使えないけど、if value != null
で、そのブロック内で変数がノンオプショナルになるんだ。これがSwiftの if let value = value
と同じような動きです。Kotlinの場合は、その変数がちゃんと値があるよということになる。
Swiftでは値が取れた際にリリースしたい時など、自己プロパティだったら別に大丈夫なんですが、名前空間がない時、例えばオプショナル値が取れた際にはリリースしたい場合、シャドーイングしてしまうとオプショナルじゃなくなるので注意が必要です。 xにnilを代入することはできないので、名前をyに変えたりして処理します。yにxを代入する形ですね。このシャドーイングの名前さえ変えられれば、Kotlinでも問題ありませんが、実際にはそんなことはほとんどしないでしょう。Kotlinはそもそもそのような仕様がありません。まあ、面白い仕様だと思います。
理想的にはKotlinがわかりますよね?一応、わかりますけれども。このif文の中でnilを代入することはあまり普通ではありません。確かにクラスとかが管理していて、それに対してnilを代入することは稀にありますが、あまり一般的ではないです。
ところで、Google Apps Scriptを使ってみたんですが、初めて利用しました。Google Apps Scriptではコメントで型を指定したりすると、標準のエディターで補完が効くようになったりします。コメントの書き方は忘れてしまいましたが、「@param」でそのパラメータの型がString型ですよ、と指定します。このように書くと、関数内でArg1がちゃんと文字列型として補完されるようになります。これを見て、「なんで補完が出てくるんだろう、Arg1なんて変数あったっけ?」と思いましたが、これはXcode13の新機能で、いろいろと自動補完が出てくるんです。コメントに書いたからではなく、勝手に判断して補完が出てきたようです。
Google Apps Scriptでは他にも、if文の中で「if Arg1がinstance of なんとかの型」のように記述すると、そのArg1がブロック内ではStringとみなされて補完されるという面白い仕様があります。Kotlinも同じような感じですね。
ところで、Arg1はライブラリで定義されているようですね。これが補完に関係していたようです。Xcodeがいろいろな可能性を探って補完をかけているのかもしれませんが、正確なポイントはわかりません。
インポートしていない場合でも、「ディスパッチQ」関連の補完が勝手に出てきたりします。今の時点では明確に出てこないかもしれませんが、インポートしていない場合にはインポートしましょうと言われたり、タイムゾーンなども勝手に補完されることがあります。これは、たとえばFoundationで定義されているものだからです。
これからわかるように、こういった自動補完は予測変換のようなもので、非常に便利です。確かに厳密なプログラミングの世界において、新しい風として面白い発展ですね。
このような自動補完の機能は、「ファジー」と呼ばれることがありましたが、最近ではその言葉もあまり使われなくなりました。しかし、かゆいところに手が届くような仕組みは、非常に便利です。
そして、大事なポイントとして、どのプログラミング言語でもブール型として評価される必要があるという基本があります。その他にも、バリューバインディングパターンやパターンマッチングも使えますが、基本はブール型として評価される必要があります。
また、オプショナルバインディングに関して、if文とletを組み合わせて値がない場合にも対応できるようにする構文があります。これはオプショナル専用の言語構文とも言えますね。if letの後に使えるというのが非常に重要なポイントです。 オプショナルネームという変数があったときに、それに値が入っていたなら実行する。その後には何が書いてあるんだ。型名の後にハテナを付けることで、ああ、これのことか。オプショナルの補足をしてるだけですね。こうやって型の後にハテナを付けることで、それがオプショナル型になってnil
も取るようになります。そして、オプショナル型のときにはオプショナルバインディングを使って評価式を書けるよっていう紹介がされていますね。
次に、全然ブール型の話。必ずブール型ですよの後に、いきなりそうじゃない例がちゃんと来てますね。まあいいや、とりあえずこれはさっきお話ししたからオッケーでしょう。
さて、どっちの話に行こうかな。あと5分しかないから、オプショナルの話をちょっといきましょうか。この変数名、余談なんですけどね。このオプショナルがあったときに、nil
が含むかもしれないという場合、APIデザインガイドラインを意識するとしたら、それを制約ではなくて、その役割で表現しないといけなくなりますよね。そのときに、このオプショナルネームという名前は明らかに制約ですね。これをちゃんと役目として名前を付けようというときに、昔はしょっちゅう悩んでいました。オプショナル、nil
を持つことがある可能性のある変数名をどうやって付けるのがいいんだろうと考えていましたが、今も見えていないんですよ。
結論から言うと、例えばこれがネームという変数を取るかなと思うんですよね。だいたいパラメータとして取るときには、ネームと取ると思います。仮にそれを引数として受け取らなかったとしても、要はオプショナル型だったとしても、そこでわざわざ引数名としてオプショナルネームとは付けないと思います。
言いたいことは何かというと、ファンクションとしてセットで、例えばネーム、要はアドレス帳みたいなものに登録していくときに、とりあえず名前を付けなくてもいいよという仕様を作るときに、こうやってオプショナル型にすると思うんですよ。このときにオプショナルネームとはわざわざ付けないですよね。というノリでいますけど、これがローカル変数だったときに果たしてオプショナルが入っているかもしれない名前を型で定義するときに、これでいいのかというのがね、昔から悩むことがありまして、今は別の考え方です。こういう書き方はしなくなっていますが、多分オプショナルの変数の名前付けって考えてみると難しいよねというのが、ちょっと余談として今思い浮かんだので紹介しておきました。
実際のところは、またもうちょっと違う。nil
も含めて取りうる名前何があるんだろうね。例えばアマウントはゼロで取れちゃうな、とにかく名前に応じてnil
が入る可能性があるよねみたいな、名前に自然に寄っていくんだろうなって最近思って、やっぱりここではネームと付けますよね。きっとね。でも多分独立することがそもそもないんじゃないかなって思うんですよね、基本的に。結局オプショナルバインディングを使ってオプショナルじゃない値に受け取ったりとか、あとはそもそも何らかのオプション、さっきのオプションズみたいな感じでキーとバリューがあって、その中でオプションズとして、最終的にはオプションズとして結局こういう風な名前、そういう具体的な書き方とするとドットネームでここがネーム型みたいになってた方が安全なのかな。まあこういう風にね、結局のところ何らかの形でそれがオプションですよみたいな別の側が付いて実質表現されてたりとか、if let name =
みたいなね、こういう風に変わっていくんだろうなと最近感じていて、独立してlet name
みたいなものはあまり置かれないのかなみたいな、そんなことをふと思ったので補足として紹介してみました。
まあ漠然とね、自分が感じている程度なので、実際にどの程度までこれに習っているかわかんないんですけど、最近はこんな指標でコードを書いていってるなぁと個人的に思ったので、ちょっと紹介してみました。じゃあ、時間になりましたので今回はこれぐらいにしておこうかなと思います。はい、どうもありがとうございました。お疲れ様でした。