忘備録-Swiftのジェネリクス

趣味でIOSアプリ開発をかじっていた自分が、改めてSwiftを勉強し始めた際に、曖昧に理解していたところや知らなかったところをメモしています。いつか書き直します。

## 参考文献

この記事は以下の書籍の情報を参考にして執筆しました。

- [詳解 Swift 第5版](https://www.amazon.co.jp/%E8%A9%B3%E8%A7%A3Swift-%E7%AC%AC5%E7%89%88-%E8%8D%BB%E5%8E%9F-%E5%89%9B%E5%BF%97/dp/481560407X)

##ジェネリクス
型をパラメータとしてプログラムに記述するための機能。
ジェネリクスを使った関数例


##型パラメータの書き方

＜T＞ : 関数定義、型定義に書くことによってTを型パラメータとして使うことを示す。
＜T,U＞ : 型パラメータが２つ欲しいときはこのように書く。
＜T: OtherType＞ : TはプロトコルOtherTypeに適合するか、クラスOtherType自体かそのサブクラスであることを示す。
where 条件 : T:OtherTypeやT==Uのような条件を記述する。

##プロトコルによる制約

```swift
func mySwap<T: Comparable>(little a: inout T, great b: inout T){
  if a < b {
    let t = a; a = b; b = t
  }
}

var a = "hoge"
var b = "piyo"

print(a,b)    // hoge piyo
mySwap(little: &a, great: &b)
print(a,b)    // piyo hoge

var c = 10
var d = 30

print(c,d)    // 10 30
mySwap(little: &c, great: &d)
print(c,d)    // 30 10

var e = ("hoge", 0)
var f = ("piyo", 100)
mySwap(little: &e, great: &f)    // error Comparableに適応してないことを指摘される。
```

##型パラメータの推論

```swift
// 任意のオプショナル型配列を返す関数
func f<T>(_ n:Int) -> [T?] {
  return [T?]( repeating:nil, count:n )
}

//let a = f(3)    // 型推論の座愛量がないのでエラーとなる
let a: [Int?] = f(3)
print(a)    // [nil, nil, nil]
```

##型パラメータをもつ構造体の定義
構造体や列挙型、クラスが型パラメータを持つように定義することができる。
定義方法は名前の後に<T>をつける。
て戯号するべきプロトコルなどの条件をつけることもできる。

```swift
// プロトコルを作成
protocol SimpleVector {
  associatedtype Element
  var x : Element { get }
  var y : Element { get }
}

// 型パラメータを持つ構造体を定義
struct Vector<T> : SimpleVector {
  typealias Element = T
  var x,y : T
}

```

##型パラメータを持つtypealias
型パラメータに複雑な制約をつけることも可能。

```swift
typealias Avatar<T,U> = (power: T, armor: U) where T: Comparable, U:Collection, U.Element == (String, T)
let fighter: Avatar = (power: 90, armor: [("Sword", 50), ("Hoge", 10)])
print(fighter.armor.first!)    // ("Sword", 50)
```

##不透明型
関数の戻り値型としてSelfまたは付属生を持つプロトコルを指定する。
例えば下記の例で返り値にRankedというプロトコルを指定するが、プロパティに含まれるRank型が何かわからないのでエラーとなる。
このような場合にプロトコルの前にsomeというキーワードを置いて、返り値の型として使用できるようになる。

```swift
protocol Ranked {
  associatedtype Rank: Comparable
  var name: String { get }
  var rank: Rank { get }
}

struct Person: Ranked {
  var name: String
  var rank: Int
}

//func comparRank<T>(_ a: T, _ b: T) -> Ranked where T: Ranked {    // error
func comparRank<T>(_ a: T, _ b: T) -> some Ranked where T: Ranked {
  let c = (a.rank > b.rank) ? a : b
  return Person(name: c.name, rank: c.rank as! Int )
}

let a = Person(name:"hoge", rank: 1)
let b = Person(name:"fuga", rank: 30)
let c = comparRank(a, b)
print(c.name, c.rank)
```

##不透明型の性質
下記のようなプログラムを実行するときsomeを使わずにプロトコルを返す関数を実装。
返り値を２通り返すような処理にしてsomeを記述するとエラーとなる。
someを指定した場合プロトコルに適合するいずれか１種類の型でないといけない。


```swift
protocol Named {
    var name: String { get }

}
protocol Ranked : Named{
  associatedtype Rank: Comparable
//  var name: String { get }
  var rank: Rank { get }
}

struct Person: Ranked {
  var name: String
  var rank: Int
}

//func comparRank<T>(_ a: T, _ b: T) -> some Named where T: Named { // error
func comparRank<T>(_ a: T, _ b: T) -> Named where T: Named {
  if (a.name > b.name) {
      return Person(name: a.name, rank: 0)
  }
  return b
}

let a = Person(name:"hoge", rank: 1)
let b = Person(name:"fuga", rank: 30)
let c = comparRank(a, b)
print(c.name)
//print(c.rank)    // error Namedプロトコルにrankがないので
```

また、some Named型が複数存在する場合、それぞれが違う型とされるので代入などできない。

```swift
func newMember(name: String) -> some Named {
  return Person(name: name, rank: 0)
}

var a: some Named = newMember(name: "hoge")
var b: some Named = newMember(name: "fuga")
a = b //error
```


##クロージャやジェネリクスを使って提供されているメソッド

###filter - 条件を満たす要素を取り出す

```swift
let a = (1...100).filter { $0 % 22 == 0 || $0 % 33 == 0 }
print(a)    // [22, 33, 44, 66, 88, 99]
```

####map - 各要素を処理した結果を配列として得る

```swift
let b = (1...10).map { $0 * 3}
print(b)    // [3, 6, 9, 12, 15, 18, 21, 24, 27, 30]
```
filterと組み合わせて使うこともできる

```swift
let c = (1...10).map { $0 * 3}.filter { $0 > 15}
print(c)    // [18, 21, 24, 27, 30]
```
###mapValues - 辞書の含まれる要素の値に対して処理を適用し、その結果とキーを組みあわせた辞書を返す。

```swift
let d = ["りんご":100, "みかん":80, "ぶどう":300]
let mapped = d.mapValues{ (value:Int) -> Int in
  return Int(Double(value) * 1.1)
}
print(mapped)    // ["りんご": 110, "ぶどう": 330, "みかん": 88]
```
###compactMap - 引数クロージャがnilを返した場合配列に含めない

```swift
let e = ["Hoge", "fuga","1", "Pochi","piyo", "99"].map { Int($0) }
print(e)    // [nil, nil, Optional(1), nil, nil, Optional(99)]
let f = ["Hoge", "fuga","1", "Pochi","piyo", "99"].compactMap { Int($0) }
print(f)    // [1, 99]
```
###flatMap - シーケンスを返すクロージャを引数とし、全てのクロージャの要素を含む配列を返す

```swift
let g = [["Hoge", "fuga"], ["dog", "Pochi"], ["piyo", "foo"]].flatMap {$0}
print(g)    // ["Hoge", "fuga", "dog", "Pochi", "piyo", "foo"]
```
###reduce - 各要素を連続的に処理して１つの値を得る

```swift
let h = ["Hoge", "fuga","piyo", "foo"].reduce("AAA"){ $0 + $1 }
print(h)    // AAAHogefugapiyofoo
```