Page icon

第321回

2023/11/15
Empty
番外編
Empty
Empty
Empty
Empty
Empty
12 more properties
今回も引き続き 
演算子
 の基本を見ていく中で気になった 
三項演算子
 についてを眺めていきます。前回まででもだいぶ観察が進んだ感じがしますけれど、その良し悪しを中立な姿勢で評価するには何かが足りない心地がするので、もう少しだけその様子を窺ってみれたらいいなと思います。よろしくお願いしますね。
————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #321
00:00 Start 00:10 今回の展望 01:50 演算の優先順位を整える 04:30 記号が多いと、読みにくくなる? 08:01 条件演算子の優先順位が難しくする 13:26 条件演算子の代わりに if 式を使う 17:18 代用できるものがあるなら、分かりにくいのを廃止する手も 28:56 次回の展望 —————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #321
では、始めていきます。引き続き三項演算子の話をしたいと思います。いま見ているのはブログの記事で、たぶんまだ中央くらいまでしか読めていないのですが、「三項演算子は悪である」というトーンの記事です。題名からして「悪」という前提で語られているのですが、世の中的にも(あくまで自分の感覚ですが)三項演算子が悪と捉えられることが多く、避けられがちな気がします。もう少し中立的に見られないかなという思いがあり、良いところ・悪いところの両方に着目して、普通に検討材料の一つとして挙げられるくらいに自分が慣れたい、という気持ちで情報を漁っています。とはいえ、このブログは少し偏っているようにも感じるので、もう少し読み進めていきたいと思います。ざっくりと見ていきますね。
適切な括弧で括る必要があるか、という話にもつながるのですが、三項演算子に限らず、最近は丸カッコを使って優先順位を明記することをあまりしていないな、とふと思いました。昔は、あえて不要でも括弧を付けることをよくしていたのですが、これが良いのか悪いのか、賛否ありますよね。
例えば、すごく単純な例として
x = 1 + 3 / 4 * 5
のような式があったとします。加算や乗算・除算の優先順位をちゃんと理解していればそのままで良いのですが、読み手に誤解を与えたくないときに括弧を入れることはあります。怖いからここにも括弧を付けよう、という感覚になることもありますが、やり過ぎると逆に読みづらくなる、という感覚もありますよね。一方で、「1 と 4 を先に足したい」という意図があるなら、
(1 + 4) * 5
のように優先順位を変更する括弧は必須で、これは意味のある括弧です。無意味な括弧は避けたいけれど、必要なときは欲しい、というバランスの話だと思います。
記号が多すぎると、短い式ならまだしも、長くなるにつれて読みづらさが増すようにも感じます。オプショナルも同様で、例えば
A = 1
があって、
A
B
が両方とも Optional だとします。強制アンラップのビックリマーク(
!
)が並び始めると、たしかに見にくくなることがあります。オプショナルは安全性の観点から早めに解消しておく、という指針もありますが、ひとまずここでは置いておきます。
例えば、
let c: Bool? = true
のようにして、ある式と別の式の値比較をして、さらに
c
の条件も加える、という複合的な論理式を考えたとき、誰もがすぐ読めるとは限りません。
==
&&
の優先順位次第で解釈が変わり得る箇所は、丸カッコで意図をはっきりさせたくなりますよね。最後に
==
で比較したいなら、そこを括弧で強調したくなることもあると思います。もちろん、「そもそも式を分割して直せ」という話かもしれませんが、言語によっては括弧で明示するのが好み、ということもあるでしょう。ただ、これもやり過ぎるとやはり嫌な感じがします。
こうした話は、三項演算子が「見にくくなる」という指摘と似ている気がします。記号が多いとどうしても視認性が落ちますし、入れ子が深くなると分かりにくいという話は、前回も扱いましたね。 入れ子は何でも分かりにくいよね、という話は前回しました。連続する入れ子は注意が必要です。C や JavaScript など多くの言語では書けるのですが、読み手が混乱しやすい書き方になりがちです。
この例については、必ずしも分かりにくいとは言えない場合もあります。ただ、PHP は三項演算子が左結合であるため、例えば
A ? B : C ? D : E
のように書くと左から結合して評価されます。つまり、いったん左側の結果を出してから、その結果に対してさらに三項演算子を適用していく形になります。こういう挙動を期待するかどうかは文化や習慣にもよりますし、PHP 的な読みを好む人もいるかもしれません。
とはいえ、言語によって動きが違うのは混乱のもとです。統一した書き方を選ぶべき理由の一つかもしれません。Apple の API デザインガイドラインにも、専門用語は、それを使わなければ誤解を生む場面など、本当に必要なときに限って使いましょう、という趣旨の指針があります。たとえば「演算子」という専門用語が必要かどうかを考える、ということです。Swift のガイドラインに合わせるとしても、「PHP と C 言語の仕様差をここで説明する必要があるか」という判断はあり得ます。そして、より簡単で分かりやすい書き方があるなら、そちらを選ぶほうがよい、というガイドラインの精神に照らせば、三項演算子も、if 文で書けるなら if 文で書く、という導き方はありだと思います。感覚的な「ぜひ」ではなく、こうした根拠があるとよいですよね。
括弧だけでは意図が伝わらないのであれば、別の書き方を選ぶべきです。ビックリマーク(
!
)が何重にも並ぶような条件式は、正直ぱっと見で分かりません。条件の否定が重なっていくと、最初の項に対して順に否定がかかっているのだろう、という雰囲気は感じられても、すぐに正確には追えないことが多いです。ゆっくり考えれば分かるかもしれませんが、そこで時間を使わせるのは避けたいところです。
とりあえず、三項演算子に派手な記号を多用するよりは、そうでない表現を選べばよい場面が多いです。Python なら
if ... else ...
の条件式があり、記号より言葉で書けるので読みやすいです。例えば、
x = a if c else b
のように書けます。最初は戸惑うかもしれませんが、慣れれば大丈夫です。言葉で書かれているほうが直感的に読みやすい、というのは確かにあります。
Swift でも、if が式になりました。中括弧が少し邪魔に感じることはあるかもしれませんが、次のように書けます。
let x = if condition { a } else { b }
これなら、三項演算子を使わなくても、意図がはっきり伝わります。この辺は何か議論もされていた気がしますが、少なくとも「読みやすさを優先する」という観点では、if 式を選ぶのは良い落としどころだと思います。さっきの Python の書き方も悪くありませんし、記号より言葉のほうが分かりやすいという感覚は多くの人に共有されているはずです。 書き方についてです。やはり
if
で「この条件のときにだけ
a
を当てる(適用する)」という形にすればよいのではないか、という感覚があります。Swift的にも、こちらのほうが自然になってくるのかなと思います。「このコンディションのときに
a
を当てる」「それ以外はデフォルト」という発想ですね。演算子がどう機能するのかを想像するより、文字で流れを書いたほうが分かりやすいこともあるので、少し工夫は要ります。
Swift のマクロで書けると良いかな、とふと思ったのですが、まだ評価式そのものに対するマクロはできないですよね。そういうタイプのものは難しくて、後ろに続く任意の式を対象にするような書き方はできません。結局パラメータにしないといけないので、
if (...) { ... } else { ... }
のような形を直接マクロで置き換えるのはダメですね。まあ、マクロの話はここでは脇に置いておきます。
三項演算子
?:
の記号で書くのと、文字で書くのとでは雰囲気がだいぶ違ってきます。
if
式(
if
を式として使える形)にできれば、本来「比較演算」を書きたかったのに「代入」を書いてしまう、といった取り違えの問題を回避できます。敵を取り違えて代入だけが実行される、ということにならないわけです。式を丸括弧でくくって明示する、というのも良い書き方だと思います。
三項演算子がどうこうと議論する暇があるなら、いっそ三項演算子を廃止して
if
式で置き換えやすくしたほうが健全かもしれない、という考え方もあります。
if
の後に丸括弧を書くかどうかで迷う人もいるので、別の書き方のほうが良い気もします。そう考えると、今の仕様でも十分に書けるし、これで良いのではという気もしてきます。実際に並べて書いてみると、間にコメントを挟むほうが読みやすい、というケースもあります。コメントを読んで納得できるなら、それは良い選択ですよね。
重要なのは、三項演算子がいさかいを生むのであれば、ほかの手法で解決できるなら撤去してしまおう、という発想です。今回の三項演算子に限らず、物議を醸すものがあるなら、それをなくしても成り立つ方法を考えるのは悪くないアプローチだと思います。イベント開催の運用なども同じ発想で、なくしてもうまくいく世界ならそれで良い、という考え方です。
そういう意味では、「三項演算子を廃止しよう」という提案がプロポーザルで通る可能性もゼロではないかもしれません。
if
式で回避できるのなら不要だよね、という発想です。たとえば、5行目の
if
式と6行目の書き方、見やすさの面ではどちらを使いたいか、という観点になります。どちらかに一律で決め打ちしなくても良いのですが、これを踏まえて新しい発想があるならそれで良いと思います。
switch
式にするとどうなるか、という話もあります。
switch
だと
case
が必要になってきますし、その後は改行しないと読みにくい、ということもあります。文字が多すぎると感じる場合もありますし、少なくともステートメントの区切りが分かりにくいときはセミコロン(
;
)が欲しくなります。区切りが分かりにくかったので
;
を入れたのですが、ここは余談でした。 5行目と6行目を見たときに、まずは分かりやすい書き方として、できれば三項演算子ではなく「if式」を使いましょう、という方向に持っていくのがよいと感じています。さらに、もしそのif式でも分かりにくいなら、通常のif文で組み立てましょう、という考え方もありかもしれません。
if文で組み立てるときの怖さとして、例えば
var d: Int
のように変数
d
(Int型)を用意して、
if
の条件でどちらかのパスに落ちたときに
d
へ代入して初期化していく、という書き方があります。このとき、うっかり別の変数を書き換えてしまい、
d
に入れるべき値を別のところに代入してしまったり、条件次第では代入が起きずに
d
の初期化をし忘れてしまったりする懸念があります。そういったミスがあると、本当は
d
を使いたいのに「初期化のし忘れ」が発生する可能性がある、というのが、この場面でif文が嫌われる理由の一つになるかもしれません。
こういう場合でも、Swiftの「確定初期化(definite initialization)」という仕組みが助けになります。例えば、初期化するパスをどれか一つでも忘れていた場合、
print(d)
の段階で「まだ初期化が完了していないパスがあります」といったエラーをコンパイラが出してくれます。さらに、変数ではなく定数の
let
で書いておけば、「まだ初期化されていない可能性がある」という指摘も受けられますし、一度値が確定するとその後に値が変わらないという保証も得られます。確定初期化があるおかげで、先ほどブログに書いてあったような「初期化が分離される」といった不安は、実際にはそこまで気にしなくてよい面もあります。
この前提を知っていると、5行目・6行目・9〜15行目にかけての選択肢を、段階的に「三項演算子 → if式 → if文」というふうに選んでいけます。そして、ここまで考えると、「三項演算子は別に使わなくてもいいかもしれない。if式があるおかげで」という見方もできます。つまり、三項演算子が登場してきたときに、それを本当に三項演算子で表現する必要があるのかを検討する、ということです。
もっとも、名前付けがしっかりしていて読みやすい場合には、三項演算子の方がif式よりも見やすいこともあります。例えば、
C
のような分かりにくい名前ではなく、
isValid
のように状態を表す良い名前、値側も
currentValue
defaultValue
のように意味が明確な名前であれば、「この状態が有効ならカレント、そうでなければデフォルト」という意図が、三項演算子でも十分に読み取れます。例えば、
let value = isValid ? currentValue : defaultValue
のような書き方です。逆に、if式なら
let value = if isValid { currentValue } else { defaultValue }
のように書けます。
5行目と6行目を並べて見たときに、どちらが分かりやすいかはケースバイケースですが、個人的には三項演算子をよく使うこともあって、6行目を選びそうだと感じます。これまでif式を使える言語にあまり触れてこなかったこともあり、if式にはまだ慣れていない感覚があります。三項演算子に慣れている人は、同じように6行目を選ぶという印象を持つかもしれません。 あと、
if
のほうだと、Racketではそういう扱いを多用するので、むしろそうしないほうが気持ち悪いという感覚もありますね。たしかに、「
if
文で処理しようよ」みたいな一般的な認識はあります。こういったときに
if
式があまり一般的でなかった(C言語系ですね)こともあって、
if
を式として使って、ここが戻り値になって、さらにそこへ代入される、という見え方がなんだか気持ち悪く感じるのかもしれません。
まあでも、これは慣れの問題な気がします。慣れないうちは微妙だと思うかもしれませんが、慣れた瞬間に「これいいね」と思えたりもします。今はどうなんでしょうね。言われてみると、なおさらそういう扱いが欲しくなってきましたが、意図としては間違ってはいないわけです。となると、無難な線を攻めるなら普通の
if
文に落ち着くのかなとは思います。ただ、それだと進歩がない可能性もなんとなく感じるので、もう少し模索したいところです。
時間になりましたね。いろいろ考え方を喋りながら進めていると、意外と読み進められないものですね。ただ、ヒントになりそうな考え方も見えてきた気がするので、次回ももう少しこの続きを読んでみようかなと思います。読み方を変えたら、なんか面白くなってきました。
そんな感じで、また引き続き、今回の話題をきっかけにいろいろ話していく形で続けていきましょう。はい、では今日はこれで終わりにしますね。お疲れさまでした。ありがとうございました。