Character
シーケンスを受け取り、Tokenを返す関数を考えます。
func convertToken(from chars: [Character]) -> Token { ...
上記のようにArray<Character>
型として引数を宣言すると、文字列からこの関数を呼び出すたびにArray型を生成するというコスト(O(N))が発生します。
// Array型を生成するコストが発生する
converToken(Array("Hello World"))
そもそもconvertToken
関数は、Character
シーケンスを受け取れれば良いので、Array型である必要はありません。
しかしながらSwiftは、Self
、associatedtype
を持つプロトコルは型宣言として利用することが出来ません。
// NG
func convertToken(from chars: Sequence) -> Token { ...
ただし型制約としては使用することが出来る。
// OK
func convertToken<T>(from chars: T) -> Token where T: Sequence, T.Element == Character { ...
また関数をジェネリクス化した場合は、引数の生成コストが0になります。
// 生成コスト0で呼び出せる
convertToken(from: "Hello World")
が、一見、何を受け取るのかわかりにくくなります。
そこで型消去ラッパー型として用意されている AnySequence 型の出番です。
func convertToken(from chars: AnySequence<Character>) -> Token { ...
以下のように文字列、配列であろうが、Character
のシーケンスを渡すことが出来、こちらも生成コストが0です。
// 文字列から
convertToken(from: AnySequence("Hello World"))
// 配列Array<Character>から
convertToken(from: AnySequence(str.soreted()))
str.sorted()
の戻り値は[Character]
です。したがってconvertToken(form:)
関数がもし以下のように定義されてしまうと、つなぐためには文字列に再度変換する必要が出来ます。
// 文字列を直接受け取ることにした場合
func convertToken(from str: String) -> Token { ... }
// 一旦Stringを再生成
convertToken(String(str.sorted()))
またジェネリクスと比較では、並べてみるとどちらがリーダブルかは明白です。
func convertToken<T>(from chars: T) -> Token where T: Sequence, T.Element == Character { ...
func convertToken(from chars: AnySequence<Character>) -> Token { ...
まとめ
Swift標準ライブラリーではAnySequce
以外にも沢山の型消去ラッパー型が用意されています。(Any*~系は型消去ラッパー型です。)
ジェネリクスを使う前に一旦そちらを使用することも検討してみてください。
参照
AnySequence型
https://developer.apple.com/documentation/swift/anysequence
型消去の使い方 in Swift
https://yossan.hatenablog.com/entry/2019/10/14/172457