https://youtu.be/PFWxrxju7hc
今回から新たに The Basics の オプショナル
について眺めていきます。Swift の根幹を支える機能のひとつなためか言語使用によるサポートも手厚いところで、見どころはたくさんありそうですけれど、今回はそんな機能の概要と Objective-C と比べたときの特徴の違いみたいなところを確認していく回になりそうです。よろしくお願いしますね。
——————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #136
00:00 開始 00:10 オプショナルと安全性 04:16 値がない可能性を表現する 06:07 オプショナルの状態表現 07:01 オプショナルは Swift からの新しい機能 08:45 値が存在しないことと、NULL ポインター 09:38 参照先不定と、NULL ポインター 11:25 オブジェクトが存在しないことを示す旧来の方法 14:14 オプショナルでなければ値は必ず存在する 15:20 オブジェクトではない値でも存在するかを表現可能 16:42 存在しないことを nil で表現 17:55 関数型でも同じ表現が可能 18:30 全ての型を第一級の型として表現可能 18:50 オプショナルができなかった頃の表現方法 19:52 NSNotFound 20:24 プリミティブ型とマジックナンバー 21:31 オプショナルを使わなかったことによる複雑化 23:32 オプショナルによって nil をシンプルに扱える 23:55 SwiftUI で NSNotFound 絡みの問題があったらしい 24:56 所感とクロージング ———————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #136
はい、では始めていきますね。今日から新たに「オプショナル」の話をします。Swiftにとっても大事なトピックですよね。10年前の話になりますけれど、Swiftはオプショナルを安全性という観点で打ち出しました。当時、特にiOSアプリやmacOSを作っている開発者にとって、Objective-Cばかり使っていた人たちには特に重要な変化でした。もちろん、C++などを使っていた人たちにも影響があったと思います。
オプショナルの登場によって、特にObjective-Cの自由度の高い世界観で初めてオプショナルに対面した人たちは、その理屈を理解しつつも、わずらわしさが増えたと感じることも多かったです。その当時の葛藤や苦労を思い起こす人もいるかもしれませんね。今では「塗る観点」という発想がすっかり当たり前になってきていて、他の言語でもよく聞かれるようになりました。
例えば、JavaScriptではnull
とundefined
の両方を禁止しないといけないという特性があります。パラメータを省略するとundefined
になるので、値がないことを主張したいときにnull
を使う、という微妙な使い分けがありますね。こういった考え方は、何を考えないといけないかを明確にしてくれ、考える力を養うことにもつながります。
しかし、実際のプロダクトの安全性を考えると、混乱の元になることもあるため、オプショナルは非常に有益な機能です。オプショナルは安全性を担保しつつも、考える力を残してくれる点が優れています。オプショナルの導入により、多くのプログラミング言語にも同様の機能が導入されるようになりました。
では具体的に、どのような話になるかを見ていきましょう。まず、値が存在しない可能性があるときにはオプショナルを使います。Objective-Cでは、値が存在しない可能性としてインスタンスがない場合が多かったです。しかし、Swiftではそれ以上の広い範囲で値が存在しない可能性を考慮しています。
Swiftでは、全てが第一級の型とされていて、基本的にオプショナルを使うという発想が一般的です。値がないという表現は、インスタンスが存在しないという表現になります。このため、オプショナルを使うことで、素の状態を表現することができます。
オプショナルには二つの状態を表現する能力があります。値がある場合と、値がない場合の二つです。値がある場合、その値にアクセスするためにはオプショナルをアンラップする必要があります。このアンラップ操作を理解することが大切です。ただ、今この話にあまり深入りする必要はないかもしれませんね。
もう少しスライドを見ながら補足していきます。オプショナルの基本概念は、値がない可能性を考慮することです。これによって、より安全で堅牢なコードを書くことができます。それでは次に進みましょう。 プログラミングにおいて、言語に精通したプログラマ、たとえばJavaなどの習熟者は、オプショナル(Optional)という概念に慣れ親しんでいることが多いです。そういった人にとって、オプショナルの概念は自然なものとして扱えます。しかし、すべての人がそうであるとは限りません。ある人にとっては、オプショナルの使用が非常に直感的でないこともあります。この差異を意識することが、メンターなどで話を進める際には有効かもしれません。オプショナルという概念は、意外と馴染むのに時間がかかるものなのです。
次に、オプショナルの使用に関して話を進めたいと思います。オプショナルは、値が存在しない可能性があるときに使います。これは、オブジェクトが存在しないことを表す場合や、ヌルポインターの発想に関連しています。昔のオブジェクト志向特にC++の発想では、ヌルポインター(null pointer)という概念がありました。C言語からC++へ移行した人にとって、ヌルポインターは普通のものです。
C言語では、ポインターというものがあり、これはオブジェクトのインスタンスを参照として持つことができます。しかし、ポインターに何も入っていない状態、つまりヌルポインターもありえます。ヌルポインターというのは、ポインターが指し示す先に何も存在しないことを意味します。C言語では、ヌルポインターは表現できますが、どこを指しているかわからないため、未定義な状態になります。
この未定義な状態になると、メモリプロテクションなどがなければ、無関係なメモリを読み書きしてしまうことがあります。これにより、悪意のあるコードが実行されることもあります。たとえば、バッファオーバーフロー(buffer overflow)が発生する場合があります。これは、メモリ範囲外をアクセスしてしまい、OSが意図していない動作を引き起こします。ヌルポインターという概念は、値が存在しないことを表すものです。
このような発想でヌルというものが描かれています。値が存在しない可能性が広がると、表現方法も異なってきます。しかし、オブジェクトのインスタンスという発想に基づいていると、その発想が制限されることもあります。意識しておくべき重要な点は、一般的にプラスの値(positive value)という概念があり、オブジェクトが状態を持っているということです。
オブジェクトがイニシャライズされると、インスタンスが存在することになります。たとえば、int status
を持つオブジェクトの場合、イニシャライズが成功すればインスタンスが必ず存在するわけです。もちろん、失敗可能なイニシャライザーなどの詳細は別の話ですが、基本的にはイニシャライズできればインスタンスが存在することになります。 インスタンスが存在しないという状態を持たなければならないことがあります。これは昔のC言語的な発想です。昔はポインターとしてオブジェクトのポインターを作り、そのポインターにオブジェクトを入れるという方法をとっていました。しかし、これは初期化されていないポインターでヌルポインターが入り続けます。このようなオプショナルを使わなければならない状態は現在では問題視されています。
ここで、ポインターが絡んでくる話になりますが、ヌルポインターだった場合には代入できないというのも面白い点です。次に、インスタンスが存在する場合にはそのままインスタンスを初期化し、値が入るという感じになります。ヌルポインターをオプショナルにしない限り、発想には上がらないのです。この「オブジェクトであるからといってポインターではない」という発想が大きなハードルとなります。
オブジェクトがないかもしれないという発想をするためには、オブジェクト型ではなくオブジェクト型のオプショナルを使用します。これにより、オブジェクト型でありながら値があるかもしれないし、ないかもしれない2通りを表現できるようになります。ヌルポインターとは全く異なる発想です。
このようにオプショナルを使えば、値が存在しないことも表現可能です。これは例えば、ファーストインデックスが取れた場合にはオブジェクトを作り値を得ます。取れなかった場合にはヌルになる、という動きになります。これがインデックスとして取れなかったときの結果として返されます。
オブジェクティブCの時代には、プリミティブ型にヌルという発想がなかったため、特殊な表現が必要でした。例えば、配列の最初のインデックスを取るときはオプショナルの整数型が返される設計になっています。これにより、インデックスが取れないかもしれない状況を想定します。同様に、Objective-Cの頃は整数型といったプリミティブ型にヌルがなく、特殊な値を使用して表現していました。 これはどういう意味かというと、ちょうどInt.max
と一緒なのかな。これはどうでもいい話で、それよりはNSNotFound
という定数について説明します。プリミティブ型はヌルという表現ができないため、必ず値がある中で、「この値は絶対に存在しないだろう」という値を設定します。
オブジェクトの場合はNSUndefined
, Swiftの場合はOptional
を使って、特定のマジックナンバーに対して名前を付け、その値が条件に合っているか判断します。この考え方は、Swiftでオプショナルに慣れていない人が初めてAPIを設計する際に発生することがあり、注意が必要です。
具体例として、オブジェクトのイニシャライズ時にオプショナルを使用する方法がありますが、プリミティブ型で同等の効果を得るには条件判定が必要です。例えば、NSNotFound
を使用して条件判定を行い、ノットイコールNSNotFound
の場合に特定のオブジェクトを扱う、というようにします。しかし、これには工夫が必要で、1行で書こうとすると難しい場合があります。
余談ですが、オプショナルがあるおかげで、ヌルを含んだ複雑な値もシンプルなコードで扱えるようになります。NSNotFound
などの特別な値を使うと、ヌル由来の問題が起こったときに、正しくヌルポインタ操作を行っていれば大きな問題は発生しませんが、特別な値を使うと誤動作が起こることがあります。
もらったリンクを開こうとしたのですが、そのときはPCの問題で開けなかったので、後で見て次回の勉強会でその話題も取り入れたいと思います。
まとめとして、値がないことを正しく表現できるオプショナルの機能は非常に重要で興味深い特徴です。時間が来たので、この続きは次回の金曜日に見ていきたいと思います。今日の勉強会はこれで終わりです。お疲れ様でした、ありがとうございました。