https://www.youtube.com/watch?v=QYV-APlFqy8
今日に扱うテーマは正真正銘「Hello, World!」です!
A Swift Tour の最初のところから眺めていきますね。
———————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #25
00:00 開始 00:58 A Swift Tour 01:32 気づけて初めて見えてくるもの 03:47 Hello, world ! 05:28 セミコロン 06:28 エントリーポイント 07:25 標準ライブラリー 09:50 標準ライブラリーのインポートが不要 10:49 標準ライブラリー以外はインポートが必要 11:40 アプリにおけるエントリーポイント 14:39 ステータスコード 15:43 fatalError で終了を通知する方法 16:46 exit で終了を通知する方法 17:50 preconditionFailure で終了を通知する方法 18:20 Darwin 20:20 Mach-O 21:03 main.swift 23:32 コマンドラインパラメーター 25:54 main 関数を自前で定義できないか調べてみる 28:47 Swift ツアー 32:02 シンプルな値表現 32:37 変数と定数 33:27 定数の特徴 36:17 変数のメモリー領域 38:31 確定初期化 40:39 定数の不変性 42:55 型に所属する定数の扱い 45:07 初期化フェーズ 51:04 Swift の変数に対する印象 53:03 次回の展望 ————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #25
【ハローワールド】
そろそろ始めていきたいなと思いますけれども、やっとハローワールドですね。ここまでよく見捨てないで来てくれたなという感じがちょっとします。珍しいですね、「やっとハローワールド」というのが。自分でもすごく新鮮です。2か月経ってやっとハローワールドに来るんだ、みたいな新しい発見を感じます。
そんなハローワールドで個人的にもようやく本格的な話ができるかなと思って、楽しみなんですけど、では早速ハローワールド行きますかね。ちょっと1個スライドを戻しますけど、これからは「The Swift Programming Language」の「Swift Tour」というものをやっていきます。
これはどういったセクションかというと、とりあえず一通りSwiftの雰囲気を捉えてみようというツアー的なお話になります。つまり第1回でやるようなお話ですよね。ここまでいっぱい大事なところ、About SwiftとAPIデザインガイドラインを見てきて、相当レベルアップした上で最初の街に着いたみたいな感じだと思うんですよ。
なので、今までだったら、いきなりここに来た場合に見えてた景色と、今から見るこの景色って全然違うと思うんです。そういったところもなんとなく自分の中で「きっとこんな風に変わったな」という視点を楽しみながら見ていくと、より理解が深まったり、新しいことを身につけたりすることができると思います。ぜひ、そういう風に捉えていってほしいなと思います。
本当に気づけるかどうかってとっても大事で、気づけないと何も思わず通るんですけど、気づけるようになると途端に身につくようになります。なので、そういった価値観でいきたいなと思います。
ちょっとコメント読もう。「2ヶ月経ってやっとABC」。ああ、そうだったっけ?もう全然忘れちゃったのですが、そっか、そういう感じね。でも大事、大事。中学校そんな感じだったか、もうすっかり忘れちゃった。確かにね。この中で若い世代はどうかわかんないですけど、自分とかその世代だと、英語を初めて触れるのが中学生で、本当に何も知らない状況だから確かにそんな感じで時間かけてやってたのかもしれないですね。
じゃあ、いきますね。
ハローワールド、新しい言語を学ぶ時にね、スライドに書いてある通りですが、お決まりのハローワールド。これをSwiftでは一行でできるっていうのが売り文句になってます。この売り文句、分かる人には分かるけど、分からない人にとっては「ああ、そう」という感じの売り文句です。
この右下に書いてあるポイント3つ。この3つが、この一行でできるっていう主張に込められてる。まず、ライブラリをインポートする必要がない。そして、グローバルスコープがエントリーポイントになる。この2つがとても大事な仕組みになってます。これによって、余計な準備、俗に言うおまじないがいらなくなるっていうところがとても大きいですね。
C言語とかでありがちなのは、#include <stdio.h>
とか書いて、その後にprintf
とか書くわけですけど、この時にint main
でパラメーターを...
プログラム例も一緒に見ていきましょう。例えば、Swiftでは
print("Hello, world!")
と書くだけで実行できるんです。これによって、ライブラリをインポートする必要がなくなるんです。
C言語の場合、以下のように書かないといけません。
#include <stdio.h>
int main() {
printf("Hello, world!");
return 0;
}
このように、C言語の場合は#include <stdio.h>
が必要ですし、main
というエントリーポイントも明示的に記述する必要があります。でも、Swiftではそういったものが不要です。暗黙的にメイン関数が実装されるので、そのまま書き始めることができます。これがSwiftのハローワールドが一行で済む理由なんです。
また、行の最後にセミコロンを書く必要がないことも特徴的です。これは、今までこの勉強会に参加してくれてた人なら数回聞いたことがあるかと思いますが、このセミコロンを書かなくていいというのは結構重要なポイントです。もはや皆さんも空気を吸うようにセミコロンを書いているかもしれませんが、それが不要というのは大きな利点です。
そんなわけで、ハローワールド見ていきましたけど、ここからが本格的なSwiftの旅の始まりです。理解が深まるともっと楽しくなりますので、今後のセッションも楽しみにしてください。 こういった形でエラーメッセージとともにクラッシュするんですよ。フェイタルエラーを使うと、リターンコードは1になります。これが一つのエラー通知の方法ですね。
次に、標準ライブラリの話をしていたんですが、Swiftでは標準ライブラリを使うためにインポートは必要ありません。これが他の言語との大きな違いです。例えば、C言語では #include <stdio.h>
のようにヘッダファイルをインクルードしなければなりませんが、Swiftでは標準ライブラリの機能を利用するために特別なインクルードをする必要がありません。
ただし、Foundationのような追加のライブラリを使いたい場合は、明示的にインポートする必要があります。たとえば、
import Foundation
とすることで、Foundationライブラリの機能を使うことができます。これは、プレイグラウンドやXcodeプロジェクトでも同じです。
Xcodeプロジェクトを使った例も紹介しましたね。具体的には、macOSのコマンドラインツールのプロジェクトを作成し、main.swift
ファイルに print("Hello, world!")
と書いてビルドします。これは、インポートなしで動かすことができます。その後、生成されたバイナリをターミナルから実行すると、コンソールに "Hello, world!" と表示されます。
ビルドしたバイナリは DerivedData
ディレクトリ内に生成されます。ターミナルでそのバイナリを実行すると、標準出力に結果が表示され、ステータスコードも確認できます。ステータスコード0は正常終了を意味し、エコーで確認することができます。
エラーハンドリングの方法として、fatalError
を使用すると、意図的にプログラムをクラッシュさせ、リターンコードを1にすることができます。この方法を使うことで、エラー時の挙動を制御することも可能です。
このように、Swiftはシンプルで直感的なコードを書くことができる便利な言語です。標準ライブラリを簡単に使える点や、エントリーポイントが不要である点など、他の言語と比較しても多くの利点があります。 とりあえず落ちた感じがしますよね。それで、エコーで「はてな」と表示するという風にやるのですが、132がどういう意味かはちゃんと調べていないです。しかし、終了コードが別のものになるため、132がおそらくフェイタルエラーなどに関係しているのではないかと思います。他にもExit1では1が返ると思いますが、これはC言語のライブラリーだったはずで、そのままでは使えません。
しかし、インポートダーウィンまで補完してくるのは驚きですね。例えば、exit(7)
とかexit(1)
でも、デフォルトの終了コードを返してくれて、実行した後に終了コードとして7が得られるという感じです。やはりフェイタルエラーだから132なんですかね。他のケースでも132を手に入れた記憶がありますが、思い出せません。忘れちゃいましたね。とにかく、こんな感じで終了コードが得られます。
プレコンディションフェイラーでも132が得られますね。これで動かしてみると、同じく132が得られるので、内部的にそんなに変わらないでしょう。もっと面白い例があった気がしますが、忘れてしまいました。まあいいか。
ちなみに、このインポートダーウィンというのは、以前勉強会でも話しましたが、ダーウィンOSというMacOSのベースになっているものがあります。その環境にCが組み込まれているようです。UnixでもLinuxでもなく、BSDがベースのものです。ダーウィンの中にCというライブラリーが入っていて、基本的にはこれだけインポートすればexit
が使えます。DarwinでインポートすることはMacの環境を想定したライブラリなので、これを使うとプラットフォームの互換性がなくなると思われます。
自分もexit
が標準だと思っていて、この辺で遊んでいたときに気づきました。exit
が使えないという点について、カーネルをどう調べるのかを確認すると、カーネル情報は環境変数やシステム関数でuname
とかやれば出てきます。この辺はOSのターミナルで動かしているのと全く同じです。時々見かけるMACHというのは多分マイクロカーネルで、BSD系のものです。ニクストステップが採用した系統で来ているダーウィンなので、間違いないと思います。
こんな風に、ターミナルのメインルーティンとして扱われるのがSwiftで、ついでにもう一つ面白いことを紹介します。普通にビルドはできますが、このmain.swift
という名前が大事です。main.swift
という名前でないとビルドエラーが起こります。急にトップレベルになってしまい、メインルーティンではなくなってしまいます。ファイル名で完全にmain.swift
とすることで、メイン関数の中に入るという仕様になっています。
実際のエラーは、メインルーティンがないよというエラーになります。どこかで_main
関数が見つからないよというエラーが出るのですが、今出なくなっちゃいましたね。とにかく、Swiftではファイル名でエントリーポイントを決めるという面白いスタイルが取られています。
また、ステータスコードの紹介に続いて、引数を取る場合についても疑問が湧くかと思います。メイン関数でパラメータを取るわけにはいかないので、それ用の標準ライブラリやFrameworkのFoundationを使います。Foundationをインポートすると、ProcessInfo
が使えるようになります。
例えば、import Foundation
を書けば、エントリーポイント13で指定した関数も自動的にインポートされるのはすごいですね。これで、どれだけ自動検出してくれるのか非常に興味深いです。ディスパッチもいけるのか試してみましたが、コアライブラリなら何でもいいというわけではないようです。おまけ程度ですが、参考になれば幸いです。 とりあえず、ProcessInfo
っていうのがCore Library、Foundation
の中にあるっていうことは、どのOSでも環境変数やパラメータを取るときにはProcessInfo
から取得できるということです。引数は文字列型の配列として得られます。環境変数も確かこのProcessInfo
から取れます。
ProcessInfo
はシングルトンになっていなくて、なぜかインスタンスを作って得る形になっています。もしかすると、ここに何か設計上の考え方があるのかもしれません。ただ、環境によってインスタンスをわざわざデフォルトで作る必要があるのなら、せいぜいstatic let
でもいいような気はしますが、とにかくこの仕組みで取れます。
さて、今ちょっと気になっていることを試してみたいと思います。以前に調べたとき、「_main
って関数がないよ」と言われたので、これを自分で作ったらどうなるか気になりました。単なる好奇心ですが、試してみます。
import Foundation
@main
struct MyProgram {
static func main() {
print("Hello, World!")
}
}
やはりエラーが出るのでしょうか。戻り値がVoid
じゃないとか、その辺が問題かもしれません。シンボルが未定義というエラーが出ているので、適切な名前空間になっていない可能性もあります。@main
属性を使うと、名前空間に関する問題はクリアできるかもしれません。
このあたりのエラーは、自分で何とかする部分じゃないので、この程度にしておきます。やはり言語自体の仕組みに任せたほうが良さそうです。
次に、このコードがプレイグラウンドじゃないということも面白いと感じました。雰囲気が一緒なので、個人的には非常に感心するポイントです。これが「Hello, World!」の実現される仕組みで、このスライドが示している内容がまさにそれです。
それでは、「Swift Tour」が始まります。このスライドの構成も面白いですね。普通は「Hello, World!」が最後に来そうなのに、順番が逆になっています。これもまた、作者の意図を感じる部分です。
このツアーで、基本的なプログラミング事例を見ながらSwiftを始めます。基本的なところですが、幅広く見ていくことで十分な情報を得ることができます。このセクションでざっと捉えた後に、『The Swift Programming Language』でさらに詳しく見ていく流れです。この勉強会も同じようにSwiftツアーでざっくりと見て、その後じっくりと見ていく方法を取ろうと思います。
ざっくり見ると言っても、流し読みではなく基本的なところを深く見ていく感じにします。一通りSwiftツアーを見終わった後、それほどボリュームはないですが、大分視野が広がると思います。この内容で十分だと思う方はそのまま進んでいただいて、さらに詳しく見たいという方は後でじっくり一緒に見ていきましょう。
公式ドキュメントの中にプレイグラウンドが置いてあるのも面白いですね。あくまでSwiftツアーのプレイグラウンドだけですが、実際にコードを試すことができます。興味がある方はダウンロードして遊んでみるのも良いかもしれません。そんなに難しいことは書いていないので、頭で理解できると思います。
では、実際のSwiftツアーを始めます。まずはシンプルな値表現について見ていきましょう。変数や定数など、基本的な表現からです。
Swiftでは、定数をlet
で宣言し、変数をvar
で宣言します。とても基本的なところですが、慣れてくるとすぐに身につくところです。そこから変数宣言などの基本的なところを学んでいきます。 身につくようなところで、このアップルの公式の解説書では、let
を定数と呼び、var
を変数と呼びますね。少なくとも自分は、往々にして両方まとめて変数と言ってしまうことが多いです。それはちょっとイメージとして頭の中にとどめておいてもらって、自分がいくら変数と言ったからといって、定数のことを言っているかもしれないというのは、ちょっと気にしておいてほしいなと思います。
今後、このセクションでは今回の話の中でちゃんと言い分けられると思うんですけど、その点で定数というものはコンパイル時に値を確定する必要はない、ここが結構大事なポイントです。人によってはここが理解できなくて、定数という言葉とlet
という言葉が結びつけられないという人も時々います。C言語で言うとconst
ですよね。でもconst
もいいのか。定数というと#define
だよね、みたいな。そういったイメージを持ってしまうと、なかなかそこから抜け出せなくなるんですけど、大事なポイントとしてはランタイム時に1回だけ値が決まって、その後動かないというのがSwiftで言う定数ですね。
なので、数学とかやっている人でしたら、定数と聞くと絶対不変な円周率とかそういったものを思い浮かべがちですけど、全然不変じゃないです。1回決まると不変という、そういう感じですね。そういう性格によってランタイム時に1回値を決定して使っていく、つまりある値に対して名前をつけて利用できるという、そういう感じの雰囲気です。
そうですね、定数イコールすなわち変更不可能というのは、なかなか雰囲気として理解しづらい人も多いと思います。でも、慣れていけば全然大丈夫だと思うので、こういうものだという感じで見てもらえればと思います。
この中でどれを詳しく見ようかな…。変数より定数ですね。定数を見ていきましょう。変数と定数の違いにおいて、定数周りでとても大事なポイントを説明します。今回は簡単に話しますが、詳しくはまた今度話しましょう。
例えば、Int
型の定数があります。こんな感じですね。
let a: Int = 0
一般的な定数だとここで初期化式も与えてあげて、その値を確定しないといけない。一般的にはこうですよね。それでコンパイル時にa
という定数が0
という値に確定して、それで使っていくという形がよくある定数なわけですよ。だから、C言語とかを知っている人だったら、static
みたいな感じかな。例えば、static int a = 0
とかね。
メモリ領域は4つあって、プログラム領域、スタティック領域、スタック領域、ヒープ領域があるんです。プログラム領域はプログラムを置くためのメモリー空間で、スタティック領域はスタティックメンバーを置くための空間。スタック領域はスタックメモリーを置くための領域、ヒープ領域もあります。それぞれのメモリ空間が予約されているんですけど、そのうちのスタティック領域にコンパイル時にメモリを確保してそこに置く、というのがスタティックです。
Swiftではlet
は少し違ってて、多分スタック領域、ヒープ領域に取ることが多いです。それでランタイム時に使うものですね。スタック領域とヒープ領域は使うために確保していく領域です。
それから、初期化式を後で書く必要がないことが重要です。それには遅延初期化(ディフィニイット・イニシャライゼーション)という仕組みがあって、初期化は後でできる。ただし使う前までには初期化を終わらせれば良いというルールが、変数や定数には用意されています。そのルールに基づいて、定数であっても使う直前までに初期化を終わらせれば使えるということです。
例えば、次のようにスイッチ文を使ってランダムなBool
の値で定数を初期化することができます。
let a: Int
if Bool.random() {
a = 1
} else {
a = -1
}
この定数a
は、実行時のランダムによって-1
の時もあれば1
の時もあるという、そういった定数になります。 ここで、なかなか厳密な意味で定数じゃないと感じる人がいるので、時折転送が起こってきたりするんですけど、まあ、考え方によってはそう捉えられてもしょうがない部分もあります。そういう人には、ゆっくりと教えてあげる必要があります。意外とこれは新しい考え方なので、分からない人がいても仕方ないかなという感じですね。
定数の大事なポイントとしては、一度何らかの形で値が決まった後に、その値を変えることができないという点です。ここがとても大事なポイントです。これによって、あらかじめ確定された値を一生使っていけることが保証されます。これは言語の仕様によって保証される部分で、非常に重要です。
Zoomでリンクが共有されているので、興味がある人は後で見てください。Zoomが閉じた後も、そのリンクを自分のNotionに貼っておくので、後でゆっくり見てもらえれば大丈夫です。
基本的には、Swiftでは変数と定数を特徴に応じて使い分けていきます。ただし、基本的には定数を使っていくことを推奨します。これは、Swiftを学ぶ上で非常に有益だからです。練習の際には、意識的に定数(let
)しか使わないようにすることで、Swiftらしい書き方が徐々に身についていくと思います。
面白いコメントが寄せられていて、「クラスの中でlet
を付けたら、値を決めないといけないのはなぜか?」という質問です。これは非常に面白いポイントで、基本的な考え方は一緒です。
まず、コードの基本構造を復習しておきます。「変数」や「定数」を使う際には、初期化、宣言、割り当て、参照という3段階があります。まず、1行目に「宣言」があります。その後、「初期化フェーズ」で値を初めてセットします。この初期化フェーズが終わると、晴れて参照できるようになります。この3ステップが基本です。
「参照できるようになる」というのは、リードだけではなくライト(書き込み)もできる状態を指します。ただし、定数の場合はリードのみが可能です。変数(var
)の場合にはライトも可能です。初期化フェーズは最初の値を決定するフェーズで、その後が代入フェーズになります。この2つの意識的な区別がとても大事です。
Swiftでは、例えば let a = 0
と書くことで宣言と初期化を同時に行うことができます。一方で、宣言だけしてその後で初期化を行う方法もあります。これを「Delayed Initialization(遅延初期化)」と呼びます。この考え方を理解すると、クラスや構造体のイメージがつかめると思います。
具体的には、クラスや構造体があり、let a: Int
とだけ書かれた場合、それは宣言だけが行われ、初期化がまだ終わっていない状況です。Swiftでは、クラスや構造体のインスタンスが生成される際に、初期化が完全に終わっているという約束があります。このため、イニシャライザーの中で初期化を完了させる必要があります。これはとても大事な制約です。
これがlet
定数についての基本的な考え方です。不明な点があればまた質問してください。 なので、せいぜい先送りできるのはイニシャライザーの中まで、という考え方は通じますかね。どうでしょうか。ここで同じ書き方ができるわけです。「True」だったら「A」はある、「False」だったら「A」はない、といった具合です。先送りできるのはこのイニシャライザーの処理ブロックが終わる寸前まで、という風に捉えると、多分どっちも同じ雰囲気で使えている、ということが今は分からなくても、だんだん多分見えてくると思います。
大事なポイントは「宣言」と「初期化」、この2つをしっかり意識することです。
分かりました。ありがとうございます。
宣言と初期化が同時に行われているというのが、意外と見えないんですよね、普通に書いていると。そのあたりが大事です。さらに初期化を分けられる、このSwift特有の特徴は興味深いですね。C言語だったら分けられはするんですが、C言語はそもそも初期化が必要ないので、独立しています。Swiftは独立していませんが、こういう約束でしっかり初期化を保証していく、というところが面白いです。Swiftの変数の特性ですね。
そうですね、Swiftの暗黙ルールに感じる部分は確かにあります。Swift言語はマネジメントしてくれるので、それに任せざるを得ない部分がどうしても出てきます。そのため、暗黙的なルールで分かりにくいと感じることもあるかもしれません。しかし、論理的にしっかりとマネジメントしてくれるので助かります。Objective-Cのように勝手にゼロ相当の値で初期化するような暗黙ルールはほとんどないので、その面では違いますね。
今回のお話も、Wikipediaで言語に問わず「変数」というものを調べると、そのフェーズが出てきます。3段階のフェーズがあります。現代コンピューティングの常識的な価値観として、変数を取り扱う際に、これが哲学的にマネージされているからです。だから、情報処理技術者の基礎をしっかり押さえていくと、気持ちが分かってくると思います。簡単に言うと意外と難しいところもあるのですが、考えていくと的を射た動きをしていることが分かってくると思います。
では、時間がいい感じなのでこれくらいにしましょう。あと30秒ほどありますが、何かありますか?
どうぞ。
結構わかりやすいというか、結構ミックスにはなっていますよね。
そうですね。使う前に必ず値が入っていることさえ保証されていれば基本的には大丈夫です。Swiftはバグを防ぐために警告を出してくれるので、親切です。それに比べて、初期化されていないとか、0で初期化してくれなくて前のメモリの値が残っているなど、他の言語で起こりうる問題が少ないです。
Objective-Cでは、インスタンス変数はすべて0で初期化されるのが素晴らしいですが、この辺りはコンパイラに依存しています。Objective-Cコンパイラはやってくれますが、他のコンパイラはやらない場合もあります。なので、クラスに関連付けたインスタンス変数しか初期化されません。他は0フィルしないので、時々取り違えて厄介なことが起こったりします。
では、こんな感じで今日の勉強会を終わりにしましょう。次回は連休があるので、次の月曜日にまた続きを見ていこうと思います。お疲れ様でした。ありがとうございました。