Page icon

第303回

2023/09/29
Empty
時事的な話題
Empty
Empty
Empty
Empty
Empty
12 more properties
引き続き Swift 5.9 で新たに導入された機能まわりを見ていく回にしますね。今回はその中から大々的に打ち出されている3機能のうちの2つ目にあたる 引数パック 、いわゆる 可変長ジェネリクス のところを見ていこうと思います。こちらも理解が及んでいないところがありそうなので、Playground で感触を確かめたりしながら進めてみようと思っています。よろしくお願いしますね。
——————————————————————————— 熊谷さんのやさしい Swift 勉強会 #303
00:00 開始 01:13 今回は可変長ジェネリクスがテーマ 01:56 コードの組み立て方が変わる印象 03:08 引数パックが解決しようとしているもの 04:59 引数で対等な値が複数のときはラベルを省略 06:47 each と repeat 08:14 引数パックを使ってみる 11:21 可変長ジェネリクスで比較演算を実装してみる 17:42 引数パックの仕様 22:06 実際に自分で使おうとすると難しい印象 23:02 それぞれの要素を比較するコードを組み立てる 27:12 複数の引数パックは、同じ数で構成される 28:58 クロージング ———————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #303
では始めていきますね。今日も引き続き、この勉強会の一環として、Swift 5.9の新機能に寄り道しようと思います。最近、いろいろとプレビューリリースされて気になるところですし、Xcodeでも動かせるようになりましたので、その辺りを見ていこうと思います。
今回で3回目か4回目になりますが、主要な件については前回で基本的なところを見て終わらせました。今日はSwift 5.9の新機能の一つ、パラメータパックスについて見ていこうと思います。これは「可変長ジェネリックス」とも呼ばれ、一時期、一部の界隈で話題になったことがあったかと思います。
パラメータパックスを使ったことがある方もいらっしゃるかもしれませんが、私も少し試してみたところ、若干難しかったです。感覚を掴むまでに少し時間がかかりましたし、慣れないと使うのが難しいという印象を持ちました。また、パラメータパックスを使うとコードの書き方が少し変わる感じがありました。見た目は大きく変わらないのですが、手続き型から関数型に変わるような感覚的な差があり、APIのデザイン自体は変わらないものの、実際に実装する際には癖がありました。最初はうまくコードが書けないかもしれません。
それでは、Swift 5.9のパラメータパックスの重要な部分について、リリース文書から見ていこうと思います。昔は複数のパラメータ、同じ型ではなく別々の型のGenericsを使って返すような場合、一つの場合、二つの場合、三つの場合とオーバーロードしなければなりませんでした。同じ型の場合は可変長の引数を使って対応できましたが、違う型の場合は難しかったです。
具体的なコード例を見てみましょう。例えば、以下の関数があります:
func bind<T>(_ a: T, _ b: T) -> (T, T) { return (a, b) }
この関数は同じ型のパラメータならば問題なく動作します。たとえば、
Int
型と
String
型のような場合:
let result = bind(1, "Hello") //これはエラーになります
これは型が一致していないため動作しません。従来はこういった場合にオーバーロードを用いる必要がありましたが、新機能であるパラメータパックスを使用すると、この問題を解消できます。
具体的には、
repeat
というキーワードを用います。それぞれのジェネリックパラメータを繰り返し展開する感じです。以下にイメージを示します:
func bind<each T>(_ args: repeat each T) -> [Any] { return [repeat each args] }
この新しい構文を使用することで、複数の異なる型のパラメータを扱いやすくなります。今までと同じようにオーバーロードを多用せずに済み、より柔軟なコードが書けるようになります。
以上がSwift 5.9のパラメータパックスに関する概要です。この機能を使いこなせるようになると、より効率的なコードが書けるようになるでしょう。しばらく慣れるのに時間がかかるかもしれませんが、ぜひ試してみてください。 ジェネリックパラメータを丸括弧で括ることによって、リピートの各要素が展開されるイメージになります。このリピートの感覚をしっかり掴んでいくことが重要です。理解が進むと、ジェネリックを活用できるようになります。
実装においてもリピートを使い、そのリピートが指定されたステートメントが要素分展開される仕組みになります。全てがリピートで展開されていくような感じです。これを踏まえてコードを書いていくと、
eacht
に対してリピートを適用します。例えば、
リピート(eacht)
と書いて中身を展開していきます。
書いたコードを実行すると、リピートされた要素が動くことが確認できます。例えば、
x
,
y
,
x
の3つが繰り返し動くイメージです。これまでタプル化する方法について苦労していた部分も、自動的に処理されるようになります。パラメータパックス(Parameter Packs)は、複数のパラメータを一つにまとめて扱うことができる機能です。
具体的なコード例としては、比較関数を作成します。以下のように、タプルの左辺 (
LeftHandSide
) と右辺 (
RightHandSide
) を比較する場合、各要素がイコタブル(Equatable)である必要があります。
func compare<T: Equatable, U: Equatable>(lhs: T, rhs: U) -> Bool { return lhs == rhs }
この場合、ジェネリックパラメータがイコタブルであることを確認する必要があります。このように、リピートを使って展開し、各要素の比較を行います。
リピートを使うときには、カンマ区切りで展開されるようなイメージです。複数のパラメータを一つのタプルにまとめることで比較処理を実行できます。リピートによって各要素が個別に比較されるため、この方法を採用する際には注意が必要です。
全てのコードが正常に動作することを確認したら、各要素が順次展開されて比較されることが重要です。これにより、ジェネリックパラメータを効果的に扱うことができます。 これを何とかしないといけません。リピートすれば何とかなるのでしょうか。リピートするとここでどうなるのか、これを明らかにしてみましょう。
inside function
、これは関係ないかもしれません。さて、こうするとそれぞれ比較してくれます。どうでしょう。
このエラーも異なりますね。ダメです、ここから全然分かりません。何か答えが出ているでしょうか。もう少し試してみたいと思います。リピート
1 left hand side
というと、left hand side が各々に分解されます。分解され、それを何とかするということですね。今だと考えが浮かばないです。
プリント、ここですね。リピート
1 left hand side
かな、これは関係ないですね。完全に分かりません。この中からリピート、これだと引数が全部渡っていく感じですかね。
Any
に変える必要があります。リピート
1 left hand side
と言っていますね。リサンフォルト、残しておきましょうか。
こうすると、ここでしょうか。
Any
as
Any
、どっちかな。こっちでしょうか、逆でしょうか。リピート
1
、ここかな、ここが変ですね。リピート
1
、こっちか。リピート
1
、この
Any
に変えたもの、自分はまだ分かりません。でもとりあえずこれで動かしてみます。
これは良いかな。例えば
1
と、こちら
x
y
。そうすると
x
y
がプリントされて
false
が返ってきます。でもタプルが出てるばかりですね。そうか、これがタプルで
Any
にした感じか。それを引きこたせたかったのですが。プリントにリピート、そうするとリピートプリント
1 left hand side
as
Any
かな。こうすると普通は横に渡っていると思ったけど、エラーが出ました。何でしょう。これはたまたまエラーが起こしているのでしょうか。
この書き方間違っているかもしれません。何を見てみる、これも実行中か。分かりませんね。とりあえずまだちょっと分からないです。どういう書き方が新しいのか。とりあえずリピートというとこのステートメントが要素の数だけ動くみたいな感じだと思います。とりあえず見ていきましょう。
プロポーザルの方ですね。こうやってオーバーロードしなきゃいけないのがパラメータパックスと
zip
って名前ですね。シーケンス、リピート
1s
、変調
zip
、面白いですね。これもリピート
1s
、シーケンス、シーケンスを任意の数取ってそれを
zip
しちゃおうってことですね。戻りて書かれていないけど
1s
のエレメントなのかな。ちょっといろいろ見ていきましょう。
ペアになっちゃった。ファースト、セカンド、また別の例ですかね。リピートペア
1ファースト1セカンド
。なるほど、ペア型にして
1ファースト1セカンド
、これをリピート。ペアがリピートしていく感じですね。APIの宣言は比較的分かりやすいですよ。実装が書いてあったリピートペア
1ファースト1セカンド
、こう言われるとこういったものは分かりやすいです。
タプるのパックとかこうやってイニシャライズのパックとか、こういうふうに一行一発でできるようなものはイメージしやすいですね。こうやって型に対してなのかインスタンスに対してなのか分かりませんが、パックされているものは一時的に展開できるから、その一時がリピートした単位で繰り返し実行されていく感じが分かりやすいです。
そのノリなんですけど、うまくいかなかったですね、さっきの。とりあえず
Aria BigType 8 HTTU
の今のは何の話か分かりませんが、こういうやつですね、HTTU。実装がもうちょっと書いて慣れていかないと分かりませんが、あまり実装が出てこないですね。プロポーザルとキャットトレーディング。
APIは大丈夫なんです。適当に作ってみると実装が感覚が複雑で実装が出てこないな。でもこの辺りはちょっと難しそうな気もします。HQRS で
Where hq ==
こういう制約もできるということですね。
==
append repeat 1R append repeat 1R repeat 19
か、リピート
1S
String Bool
。書かれているとなんとなく分かりますが、自分で書くときにはしばらく苦労しそうという感想は変わりません。
実装があった、
word1U
U
Any
ですね。
U
がリピート
1U
、だからこれは普通に任意の数の
U
が取れて、それでタプルファイに渡しているというか、そのタプルファイってどこかで定義しているんだと思うんですけど、なるほど。オーバーロードもできるみたいですね。このより適切な API にマッチしていくってことは変わらなさそうです。 だいたいこんな感じですね。これで実はうまくいったはずなんですけど、ここで比較が動いていないですね。一回Xcodeを落としてみましょうか。どうすると比較できると思いますか?それぞれの要素をバックして比べていく。なるほど、まずプリントをなんとか成功させたいんですけど、プリント文がうまくいかない感じがありますね。それぞれの要素を
Any
にキャストしてプリントしてみるのはどうでしょうか。でも、エラーは微妙な感じですね。
どうしましょう、とりあえずそれぞれの要素を比較する場合は、
leftHandSide
rightHandSide
をリピートすればよさそうですね。丸カッコは必要ですかね。とりあえず入れてみましょう。ここまではよさそうですね。ただ、これだけだと途中で浮いてしまうので、まとめたいですね。何にしましょうか。配列、あるいは複数のパラメータを取るやつでしたっけ。
initializer from
、これはシーケンスか。
ArrayLiteral
、これは用途が違いますね。
一つの方法としては、バーでバーッとくるわけですよ。値を
Bool
の配列として保持し、ここに比較結果を
values
に追加していく。どうなりますかね、これ。ここまではコンパイルは通るかな。
Playgroundで試してみましょう。メイン関数はこれだ、こうしてビルドをかけて。ビルドは成功しましたが、まだ比較の結果を使っていないのでプリントしてみます。すると結果は
false
ですね。
values
に比較結果が入っているので、全ての値が
true
であれば一致ですね。ビルドをかけると
true
が返りましたね。
具体的に試しますか。値を
1
A
、そして
1
B
で比較すると、これは
false
ですね。
A
にすると
true
になります。この過変調について、右側を
true
にしても結果は当然
false
ですし、左側を
true
にすると結果も
true
になるのは同じです。このようにして過変調を使うことができます。
これで一旦基本的な方法としてはバーを使わない方法です。基本的にステートメント単位でリピートするという特徴があるので、バーを使う機会が増えてきます。1単位として結果を保存しておく、そんなふうに考えます。この方法を使ってパラメータパックを活用し、過変調ジェネリクスを使うことで、APIをシンプルかつ任意の数だけ取得するような方法が見えてきます。ただし、同じ数だけリピートしているので、この場合は同じ数でないとダメです。
同じ数でないと、
convertできない
と言われます。これは、
leftHandSide
rightHandSide
が一致しない場合ですね。同じ数だからこそリピートが可能になると。そのため、ここを辻褄合わせする感じになります。
これで基本的にリピートが通るはずですが、例えば1つの変数
T
ともう一つの変数
U
では通りません。ここでは、
Equatable
な型でないとダメですね。また、これも一致しない場合は通りません。
これを理解するには、使い慣れていけばわかりやすくなります。リピートの特徴をしっかり押さえて、使いこなせるようになれば、複雑に見えるジェネリクスもそれほど難しくはありません。
そんなわけで、時間もきましたので今日はこれで終わりにしましょう。お疲れ様でした。ありがとうございました。