https://www.youtube.com/watch?v=-Iu7cV9RSAA
今回も Swift.org の About Swift
を眺めていきます。今回は Objective-C から見た Swift の追加機能
という観点で、目新しい機能をおさらいしていく回になります。これまで少し駆け足気味に見てきていましたけれど、せっかくたくさん開催できる機会に恵まれているので、ゆっくりじっくり眺めていこうと思います。よろしくお願いいたしますね。
——————————————————————— 熊谷さんのやさしい Swift 勉強会 #4
00:00 開始 00:48 Swift の機能 02:18 ポインターと統合されたクロージャー 08:51 タプル型 09:58 Void 11:46 複数の戻り値を返す関数 13:58 関数ポインター 22:37 要素がひとつのタプルはない 25:45 関数型にはラベル名を含められない 28:44 タプルスプラット 32:14 構造体とタプルの違い 39:20 タプルによる値のスワップ 46:04 ジェネリクス 56:56 次回の展望 ———————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #4
じゃあ、始めていきましょうか。今日は前回の続き、「About Swift」の部分から、Swiftの機能について見ていくことになります。1時間の中でどれだけ話せるか分かりませんが、前回は駆け足気味でしたので、今回少しゆっくりと見ていくことにします。Swift.orgに記載されている「About Swift」ですね。その中の機能部分を見ていきます。
前回は概要をざっと見ましたが、今回は追加機能のところを見ていきます。ここで言う追加機能というのは、SwiftがObjective-Cの後継言語として登場してきた背景もあり、Objective-Cから比べて先進的になった点について述べられています。具体的には8つの機能が挙げられていて、それをじっくり見ていきます。
まず、最初のポイントとなる「ポインターと統合されたクロージャー」について見ていきます。ここで言いたいのは、Objective-Cでは関数ポインターとブロック、メソッドが別物として管理されていたのに対して、Swiftでは関数ポインターとクロージャーの扱いが同等であるということです。この新しい機能についてプレイグラウンドで試してみましょう。
まず、Swiftで重要な二つの要素について説明します。Function(関数)とClosure(クロージャー)です。Functionは皆さんご存知の通り、何かを定義して実行するものです。また、Closureは変数にクロージャーを代入して使用できます。たとえば、下記のように書くことができます。
func simpleFunction() -> String {
return "Function in Swift"
}
let closureExample: () -> String = {
return "Closure in Swift"
}
上のコードでは、simpleFunction
が関数、closureExample
がクロージャーとして定義されています。Swiftではこれらが対等に扱えるため、例えば、次のようなコードも可能です。
let someFunction: () -> String = simpleFunction
let someClosure: () -> String = closureExample
これが、関数とクロージャーが同等に扱えるメリットの一つです。この機能のおかげで、配列のマッピングのような関数にも同等に利用できます。以下はその一例です。
let values = [1, 2, 3, 4, 5]
let mappedValuesWithFunction = values.map(simpleFunction)
let mappedValuesWithClosure = values.map(closureExample)
次に、タプル型と複数の戻り値が新機能として挙げられています。タプル型はSwiftに馴染んでいる方はお馴染みかもしれませんが、何らかの変数を定義するときに、複数の型を組み合わせて定義することができます。
例えば、以下のように定義します。
let person: (String, Int) = ("Alice", 30)
このコードでは、person
がタプル型 (String, Int)
として定義されています。これにより、複数の値を簡単にまとめて扱うことができます。以上がタプル型の基本的な使い方です。
もし他に思いつくことがあれば、いつでも戻って話題にしていただいて構いません。次に進みますが、何か質問があれば、遠慮なくどうぞ。 ちなみに、何も値を取らないタプルのような書き方もできるようになっています。おまけですが、タプルの型がVoidになっています。実際に定義をたどるとわかるのですが、これは何もないタプルのエイリアスです。なので、Void
と書くことと、型に丸括弧を何もない状態で書くことが同じになるのが個人的には面白いところでした。
通常、値を返さない関数にはVoid
を使いますが、このVoid
をタプルとして表現することも可能です。あまり使われないかもしれませんが、余談として紹介しておきます。
タプルは複数の型をまとめて1つとして扱える型です。戻り値の型は通常1つだけに限定されますが、タプルを使うことで複数の値を返すことができます。例えば、let result = action()
のように書くと、戻り値の最初のものや、もう1つのものを取得できます。
さらに、タプルには前回お話ししたようにラベルを付けることもできます。例えば、戻り値においてcode
やmessage
といったラベルを付けることで、タプルの要素に名前を付けてアクセスできるようになります。このようにすることで、可読性が大幅に向上します。ラベルがあることで、ステータスコードだなと直感的に理解できるのが大きいです。
次に、関数ポインターについても触れておきます。関数ポインターはC言語で使用される概念で、int
型のポインターのように扱えるものですが、Swiftではこのようにint
型に関数を直接入れることはできません。Swiftでは型の安全性が重視されており、C言語のようにポインターを自由に扱うことはできません。ただし、クロージャを使うことで似たようなことが実現できる場合があります。
ボイドポインター(void*
)を使った記述もCでは一般的ですが、Swiftではエニー型(Any
)がその代わりとなるかもしれません。クロージャを使って、何も返さない関数を定義し、それを変数として使うことも可能です。
以下のようなコードでその例を示します。
let nothing: () -> Void = {
// クロージャの中身
}
このようにしてnothing
を必要なときに呼び出すことができます。Swiftではポインターの直接操作は避け、より安全で抽象化された方法で同様の処理を行うことが推奨されます。 確かに、これがボイド型のポインターとも言えるような、言えないような感じがします。しかし、関数型を取っているので、ボイドとは違うかなという感じですね。関数型を取っているからボイドとは違うかなと。違和感は感じますね。確かに違和感は感じるような気がします。
ボイドのポインターをそもそもSwiftで考えていくと、混沌としていきそうですね。あと、Swiftで純粋にポインターというと、思い浮かぶのがUnsafeポインター
かなと思うんですけど、こういった書き方もできるんですかね。Unsafeポインター from opaque pointer
。Opaqueポインターとか、ちょっとややこしいかもしれません。この辺りをいじっていくと、ポインターとしてみんながしっくりとくるようなポインターができていくかもしれないですね。
Opaqueポインターに変換ってどうするんだっけ。逆に、CとかC++をやっていない人は、そもそもポインターという概念がまず分からないじゃないですかね。ここも面白いことですよね。Swiftが登場する前にいわゆるコンパイラー言語を使っていた人は、ポインターって結構ね、言われれば分かるものだったかと思うんですけど、これがSwiftが7年くらいあるうちに、ポインターって後回しでよくなったというか、知らなくてもよくなったというか、そういったところを感じますね。
ポインターと言われたときの発想、イメージ、概念みたいなものも特にオールドスタイルのプログラミングをやってた人は、ブラッシュアップしていく必要があるかもしれないですね。これを関数ポインターだと認識できることが大事になってくるのかもしれないですね。もしかすると。この辺りはきっとみんなそれぞれ思うところがあるでしょう。
あとAppleの面白いところは、内容が0個が2つ以上あればいいんですけど、逆に内容が1個だけっていうのがないんですよね。そうなんですよね、そうそう。絵を描いたときにこういうやつですよね。昔はあったんですよね。これがね、昔はあったけど今は動きはするんですけどね。内部的にint型1個とまったく一緒。だからこうやって描いたのとまったく一緒。で、まったく一緒っていうことは代入もできるっていうね、こういう感じ。これは区別しないほうが何かと良かったんだったかな。
ごめんなさい、これだと今AppleじゃないからXXX.0
が使えない、そうですね。あとラベルもつけられない。うんうん、確かにそういう大きな差がありますね。だからこうやって、こういう描き方自体はできるけど、ラベルとしてバリみたいなことができない。シングルタブルにラベルはつけられませんよって書いてありますね。
たまにラベルが使えないのが微妙に困る感じです。ラベルってかなり便利ですからね。これが何だっけ、関数の何かの場面で仮が戻り値でしたっけ。そうですね、戻り値。でもここではできてますね。何かで確かラベルそこでは使っちゃいけませんよみたいに言われることがあるんですよ、確か。ちょっと思い出したらまた触れますけど、ラベルが使えないっていう場面はなかなか困ることがありますね。
まさにクロージャーの型の引数を定義するところは確かにできない気がします。クロージャーの型の引数、ああそうですね、そうだ。だから今回の例だとF =
って書いたときに、あれ、ちゃんとどこかわからなくなってしまった。クロージャーでここからどういうコードって導いてもらえますか。
コードint
とmessage string
で戻り値を普通にボイドとか、メッセージストリング。クロージャーの中にはできますが、ここはできますね。呼び出しが使えないんですよね、確か。クロージャーのタイプのほうで。ああ、そうですね。だからここで、こういう書き方でしたっけ、頭がうまく働かない。これ確かできないかな。これはできないですね。
時には困りますね。やっぱエラーとして、やっぱラベルは入れちゃいけませんって出ますね、そうだそうだ。これです、これです。これができないっていうことは、ラベルを省略せざるを得なくなって、そうするとFを使いたいっていうときに見にくい。可読性が落ちてくるんですよね。ですね。このFに引数100OKみたいなのを渡すとき、やっとおかげさまで思い出せました。そうそう、これね。ここでね、やっぱりラベルに慣れてしまうとこう書きたいんですよね。そうそうそう。
これができないって結構致命的ですね。こういうのを見ちゃうと、ここじゃやっぱり関数型は使わないでおこうかなとか、そういう発想になりますよね。このラベル確かに大きな問題点ですね。 タプルについての説明は以上になりますかね。あと、話したいことはいろいろあります。せっかくなので話してみましょうか。タプルの面白いところや利点として、まずタプル型の変数やタプル型を持つ配列についてです。
例えば、2つの値を取るタプルがあったとしましょう。これを受け取る関数を定義することができます。一応、F
という関数がその例です。例えば、Values.map(F)
とするとしましょう。タプル型の値を関数やクロージャに渡すことはイメージしにくいかもしれませんが、例として以下のようなコードを考えてみましょう。
func action(code: Int, message: String) -> String {
return "\\(code): \\(message)"
}
この関数に対してタプル(例えば (200, "OK")
)を直接渡すことができます。このように、タプルの値と関数の引数リストが同等に扱える点が重要です。これにより、以下のように書けます。
for value in values {
print(action(code: value.0, message: value.1))
}
この書き方と同等な操作が、タプルを使うことで簡略化できるのです。関数に渡す丸括弧の引数リストとタプルの値の組が同等に扱えるという点がタプルの一つのポイントです。
タプルは値をまとめる点では構造体(Struct)と似たような役割も果たせます。例えば、以下のような構造体があったとします。
struct Status {
var code: Int
var message: String
}
この構造体とタプルは同等に扱える部分もありますが、使い回しという点では構造体の方が管理しやすいかと思います。しかし、即席で使う場合にはタプルが便利な場面もあります。
例えば、ステータスコードとメッセージをスイッチ文で扱う際にタプルが便利です。構造体の場合、比較可能であることが重要ですが、タプルの場合は以下のようにシンプルに書けます。
let status = (code: 200, message: "OK")
switch (status.code, status.message) {
case (200, _):
print("Success")
case (500, _):
print("Internal Server Error")
default:
break
}
このように、コードとメッセージをタプルとして扱うことで、より柔軟なパターンマッチが可能になります。タプルを使うことで多様なケースに対応できるという利点があります。
また、タプルを使うことで変数の値を簡単にスワップすることもできます。例えば、
var a = 1
var b = 2
(a, b) = (b, a)
このように、タプルを使うことで手軽にスワップ操作が実現できます。タプルはさまざまな場面で応用が利く便利な構文であることがわかります。
以上がタプルの使い方や利点についての説明になります。質問や追加のテーマがあれば、ぜひ話し合いましょう。 スワップを作るとできなくなる場合があるんです。スワップというと、別のメッセージコードの順にするということ関係します。例えば、A
とB
をスワップしたいとき、同じint
型のA
とB
をスワップしたいとき、スワップと聞くとスワップ関数を思い浮かべるかもしれません。これはスワップ関数のことですね。
例えば、int
型の場合、以下のように記述することができます。
var a = 5
var b = 10
(a, b) = (b, a)
このようにすると、a
とb
の値をスワップできます。この方法はタプルを使うため、スワップ関数を用意しなくても大丈夫です。
また、構造体ではこのような入れ替え操作が直接的にはできません。構造体は順番がないわけではありませんが、上から順にメモリは確保されます。この点でタプルとは少し異なりますね。タプルは複数の異なる型を一つのまとまりとして扱うことができます。
ここで、C言語の場合を考えると、スワップするためにもう一つの変数を用意する必要があります。temp
という変数を使って以下のようにするでしょう。
int temp = a;
a = b;
b = temp;
このような手続きが定石となりますが、Swiftでは簡略化されていて、頭に定石が入ってなくてもスワップだと分かる点は重要です。
次に、タプルを使った複数の戻り値について見ていきますが、ここでジェネリクスの話になります。ジェネリクスは非常に深い内容となりますが、簡単に言うと、例えばC++
のテンプレートに似たもので、自由度が制限されている分、使いやすい場合もあります。
例えば、以下のようにジェネリックな関数を作成することができます。
func swap<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
この関数は任意の型に対してスワップを行うことができます。C++
と違い自由度が狭い分、扱いやすくなっていますね。
以上、スワップとジェネリクスの基本的な概念を説明しました。続いてより具体的な応用について見ていきましょう。 こういうふうに普通の書き方としては、例えば 1 + 1
みたいな感じの合計を出すことが一般的です。しかし、これを色々な型に対応させたい場合、Int
型にこだわらず、型は何でもいいというふうにして、その何でもいい型を使って何でもいい型を返すといった書き方ができます。
ただし、この書き方はジェネリクスの書き方としては正しいのですが、実際に何でもいいとなると、それが計算できるかどうかも分からないので、型は何でもいいけれど、加算演算が使えるものに限定する定義をしてあげる必要があります。そのために、「型は何でもいいけれど、リテラルから変換できて、かつ足し算可能」という条件を加えます。具体的には、Swift に標準で定義されている AdditiveArithmetic
プロトコルを利用します。
AdditiveArithmetic
プロトコルを調べると、まず「比較可能で、かつ 0 を持っていて、足し算と引き算を持っている」というプロトコルです。0 を持っているならばリテラル変換は不要です。このように、型は何でも良いけれど、AdditiveArithmetic
に準拠しているものであれば何でも受け入れて、その計算結果を返すという関数を作れるのがジェネリクスです。これにより、整数だけでなく Double
型でも同じ関数を利用できます。
ジェネリクスがなかった頃は、これをオーバーロードで実装していました。具体的に書くと、Int
型を取って Int
型を返す関数と、Double
型を取って Double
型を返す関数など、同じ実装を書くにしても複数の関数を定義する必要がありましたが、ジェネリクスによってたった一つの定義で済むようになりました。これが Swift の大きなポイントの一つです。
ジェネリクスはプロトコル思考にもつながっており、Equatable
などがよく使われる例です。Equatable
には Self
という表現があり、自分自身と同じ型を意味します。Int
型に対しては Self
は Int
、Double
型に対しては Self
は Double
になります。これはジェネリクスの一部と捉えることができます。
他にも AssociatedType
という概念があり、たとえばコレクションプロトコルにはその要素の型を特定しないで定義することができます。これにより、プロトコルを直接扱うときにも要素の型に依存しないコードを書くことができます。
こうしたジェネリクスの概念を組み合わせることで、プロトコル思考に基づいた設計を実現できます。ジェネリクスはとても重要なものなので、Swift を始めて間もない人でも、その存在と基本的な書き方を理解しておくことが大切です。使っていくうちに徐々に身についていくものですが、早い段階から意識しておくことが重要だと思います。
はい、時間になりましたので、今日はこの辺で終了します。次回は範囲やコレクションに対する反復処理、構造体の特徴などについて学びましょう。今日の Swift の勉強会はこれで終わりにしたいと思います。ありがとうございました。お疲れ様でした。