【勉強用】Heart of Swift - 1

今更ながら『Heart of Swift』を読んだので、勉強用としてまとめる。

https://heart-of-swift.github.io/

対外的にまとめるものではないです。

第1章 Value Semantics - この記事
第2章 Protocol-oriented Programming - [【勉強用】Heart of Swift - 2](https://qiita.com/1mash0/items/12a13e99e1afff14a2f2)

# はじめに

Swiftは値型を中心としたプログラミング言語である。

Heart of Swift では Swift の中心となる概念を通して Swift という言語のコンセプトが書かれている。
中心となる概念は以下2つ。

- Value Semantics
- Protocol-oriented Programming

これらについては [WWDC2015](https://developer.apple.com/videos/wwdc2015/) のセッションの中で詳しく説明されている。

- ["Building Better Apps with Value Types in Swift"](https://developer.apple.com/videos/play/wwdc2015/414/)
- ["Protocol-Oriented Programming in Swift"](https://developer.apple.com/videos/play/wwdc2015/408/)

# Value Semantics とは
Value Semanticsの定義については [Swift リポジトリ](https://github.com/apple/swift)の中のドキュメントの[“Value Semantics in Swift”](https://github.com/apple/swift/blob/master/docs/proposals/ValueSemantics.rst)に書かれている。

"Value Semantics in Swift"では、SwiftにおけるValue Semanticsを次のように定義している。
> For a type with value semantics, variable initialization, assignment, and argument-passing each create an independently modifiable copy of the source value that is interchangeable with the source.
> 
>（和訳）ある型が Value Semantics を持っているとき、その型の変数を初期化したり、値を代入したり、引数に渡したりすると、元の値のコピーが作られて、そのコピーと元の値は独立に変更することができる。つまり、どちらかを変更してももう片方には影響を与えない。

## Value Semanticsを持つ例
```swift
struct Foo {
    var value: Int = 0
}

var a = Foo()
var b = a

a.value = 2

print(a.value) // 2
print(b.value) // 0
```

`a` と `b` は全く同じ内容のバイト列が書かれているが、異なる領域に書かれた2つのデータであり、別々の `Foo` インスタンスである。
そのため `a.value` を変更しても `b.value` は変更されない。

この場合、`a` と `b` は変更に対して独立であるため `Foo` は**Value Semanticsを持っている**と言える。

## Value Semanticsを持たない例
```swift
class Foo {
    var value: Int = 0
}

var a = Foo()
var b = a

a.value = 2

print(a.value) // 2
print(b.value) // 2
```

参照型のインスタンスは変数に直接格納されず、インスタンスが格納されている別領域のメモリのアドレスが変数に格納される。(例: `0x123ABC`)
`a` と `b` には同じ `0x123ABC` というアドレスが格納されているため同一の `Foo` インスタンスを参照することになる。
そのため、 `a.value` を変更すると `b.value` も変更されてしまう。

このような片一方を変更するともう一方も変更されてしまう場合、Value Semanticsと対比して、**Reference Semanticsを持っている**と言われる。

## Semantics vs Type
Value Semanticsは値型と(Value Type)と、Reference Semanticsは参照型(Reference Type)と深い関係があるが同じものではない。
- Value Semantics ≠ 値型
- Reference Semantics ≠ 参照型

例えば、値型だがValue Semanticsを持たない型や、参照型だがValue Semanticsを持つ型も存在する。

### 値型だがValue Semanticsを持たない例
```swift
class Bar {
    var value: Int = 0
}

struct Foo {
    var value: Int = 0
    vat bar: Bar = Bar()
}

var a = Foo()
var b = a
a.value = 2
a.bar.value = 3

print(a.value) // 2
print(a.bar.value) // 3
print(b.value) // 0
print(b.bar.value) // 3
```

このとき、`Foo`は値型なので`a`、`b`にはそれぞれ独立した`Foo`インスタンスが格納されているため、`a.value`を変更しても`b.value`には影響を与えない

しかし`Bar`は参照型であるため、`a`と`b`の`bar`プロパティには同じ`Bar`インスタンスのアドレスが格納され同じインスタンスを参照しており、`a.bar.value`に変更を加えると`b.bar.value`も変更される。

そのため`Foo`は値型であるにも関わらず変更に対する独立性を持たない、
Value Semanticsを持たないことになる。さらに、`a.value`と`b.value`は独立して変更できるのでReference Semanticsも持たないことになる。

こういった型を作らないよう注意が必要だが、以下のようなクラスのインスタンスを値型のプロパティに持たせると`Foo`のようなValue SemanticsもReference Semanticsも持たない型ができてしまう。
- NSMutableArray、NSMutableString、NSMutableData
- UILabel、UISwitch、UISlider
- AVAudioPlayer
- CMMotionManager

しかし、「ミュータブルな参照型のプロパティを持つ場合はValue Semanticsを持たない」というパターンで判断するのは危険。
標準ライブラリの`Array`は内部にミュータブルな参照型を保持しているが、Copy-on-Writeという仕組みを使ってValue Semanticsを実現している。

### 参照型プロパティを持つ値型でもValue Semanticsを持つ例
```swift
final class Bar {
    let value: Int = 0
}

struct Foo {
    var value: Int = 0
    vat bar: Bar = Bar()
}

var a = Foo()
var b = a
a.value = 2
a.bar.value = 3 // ここでコンパイルエラー
```
`Bar`を`final class`に、`value`プロパティを`let`にして、イミュータブルクラスへ変更する。(`final class`にするのは、`Bar`のミュータブルなサブクラスが作られてしまうと`Bar`型のイミュータビリティが破壊されてしまうため)
この時`a.bar.value`を変更しようとするとコンパイルエラーになる。

`a.bar`と`b.bar`は同じインスタンスを参照しているが、`Bar`はイミュータブルであるためそのインスタンスを通じて状態を変更することはできない。
そのため`Bar`型のプロパティを持つことが`Foo`インスタンスの変更に対する独立性を破壊することにはつながらず、結果として`Foo`はValue Semanticsを持つということになる。

このようなケースはよく見られ、以下のようなクラスのインスタンスをプロパティに保持してもValue Semanticsを破壊する原因にはならない。
- NSNumber、NSNull
- UIImage
- KeyPath

”Value Semantics in Swift"には、イミュータビリティを持つ場合はValue SemanticsとReference Semanticsが区別できない、と書かれている。
そのため、
```swift
final class Foo {
    let value: Int = 0
}
```
のようなイミュータブルな`Foo`クラスはValue SemanticsとReference Semanticsの両方を持つと言える。
また、イミュータブルであれば値型であってもReference Semanticsを持つと言える。
```swift
struct Foo {
    let value: Int = 0
}
```

# Value Semanticsを持たない型の問題と対処法
## Value Semanticsを持たない型が起こしがちな問題
Value Semanticsを持たない型が起こしがちな問題の例として以下2つが挙げられる。
- 意図しない変更
- 整合性の破壊


### 意図しない変更の例
```swift
class Item {
    var name: String
    var settings: Settings
    
    init(name: String, settings: Settings) {
        self.name = name
        self.settings = settings
    }
}

class Settings {
    var isPublic: Bool
    
    init(isPublic: Bool) {
        self.isPublic = isPublic
    }
}

let item: Item = .init(name: "hoge", settings: .init(isPublic: false))
let duplicated: Item = .init(name: item.name, settings: item.settings)

duplicated.settings.isPublic = true

print(item.name) // hoge
print(item.settings.isPublic) // true

print(duplicated.name) // hoge
print(duplicated.settings.isPublic) // true
```
`Settings`クラスはReference Semanticsを持っているため、`duplicated`変数の`isPublic`の変更がオリジナルの`item.settings`にも波及してしまっている。
`Item`インスタンスを複製する際に`Settings`インスタンスも複製しなければならなかった。ディープコピーしなければならなかったのに、シャローコピーしてしまったことが原因。
```swift
let item: Item = .init(name: "hoge", settings: .init(isPublic: false))
let duplicated: Item = .init(name: item.name, settings: .init(isPublic: item.settings.isPublic))

duplicated.settings.isPublic = true
```

### 整合性の破壊の例
```swift
class Person {
    var firstName: String {
        didSet { _fullName = nil }
    }
    var familyName: String {
        didSet { _fullName = nil }
    }
    
    private var _fullName: String? // キャッシュ
    var fullName: String {
        if let fullName = _fullName {
            return fullName
        }
        _fullName = "\(firstName)\(familyName)"
        return _fullName!
    }
    
    init(firstName: String, familyName: String, _fullName: String? = nil) {
        self.firstName = firstName
        self.familyName = familyName
        self._fullName = _fullName
    }
}

let person: Person = .init(
    firstName: "Taylor",
    familyName: "Swift"
)

var familyName: String = person.familyName
familyName.append("y")

print(person.familyName) // "Swifty"
print(person.fullName) // "Taylor Swift"
```
こういった処理がある時、仮に`String`がReference Semanticsを持ったクラスだった場合を考えてみる。
その時、`familyName`の値も`person.familyName`の値も`"Swifty"`に書き換えられるのだが、`fullName`のキャッシュに`"Taylor Swift"`という文字列が保存されている場合、状態更新時にはキャッシュをクリアするよう設計したのに`fullName`が出力する値は`"Taylor Swift"`のままになってしまう。
`Person`インスタンスの状態の整合性が破壊されてしまっている。

しかし`person.familyName`を直接`"Swifty"`に更新する場合は、キャッシュが適切にクリアされて問題は起きない。
```swift
print(person.fullName) // "Taylor Swift"

person.familyName = "Swifty"

print(person.familyName) // "Swifty"
print(person.fullName) // "Taylor Swifty"
```

`firstName`プロパティや`familyName`プロパティなど、`Person`のAPIを使わずに`String`のメソッドを介して間接的に内部状態が変更されるという想定しない変更のパスがあったことが原因。
Reference Semanticsはこういったパスを作りやすいため、設計時に見落としてしまうと例のように内部的な整合性の破壊が起きてしまうことがある。

## 問題への対処法
こういった問題は参照型中心の言語ではよく起こるため様々な対処法が考えられている。
よく取られる対処法としては以下の3つ
- 防御コピー
- Read-only View
- イミュータブルクラス

しかし、Swiftは参照型中心ではなく値型中心の言語であるため、値型を用いたアプローチで問題に対処する。

### 値型を用いた問題の解決方法
「意図しない変更」の例では、`Settings`がValue Semanticsを持った`struct`であればそもそも問題が起きない。
```swift
let duplicated: Item = item // 複製
duplicated.settings.isPublic = true // item.settings.isPublicは変更されない
```

「整合性の破壊」の例でも、変数`falimyName`に対する変更は`person`に影響を及ぼさないため整合性の破壊は起きない。
```swift
print(person.fullName) // "Taylor Swift"

var familyName: String = person.familyName
familyName.append("y") // personには影響を及ぼさない

print(person.familyName) // "Swift"
print(person.fullName) // "Taylor Swift"
```

さらに値型にはミュータブルクラスのように変更が容易であるという特徴がある。
つまり、値型は**ミュータブルクラスの持つ変更の容易さと、イミュータブルクラスの持つValue Semanticsのいいとこ取りをしたような存在**だと考えられる。

Swiftの標準ライブラリで提供されている型はほぼ全てが値型。
- Int、Float、Double、Bool
- String、Character
- Array、Dictionary、Set
- Optional、Result

# Swiftが値型中心の言語になれた理由とその使い方
## Swiftの標準ライブラリはコレクションも値型
Swiftのコレクションは**Copy-on-Write**という仕組みを使ってValue Semanticsとパフォーマンスを両立している。
ref: ["Copy-on-Write"](https://heart-of-swift.github.io/appendix/copy-on-write)
```swift
var a = [1,2,3]
var b = a
a[2] = 4
print(b) // [1,2,3]
```

## Swiftは値型の使い勝手を向上させる言語仕様が豊富
### `inout`引数
`Int`型の`points`変数を持つ、値型の`User`がある時、ポイントを消費する処理は以下のように書ける。
```swift
func consumePoints(_ points: Int, of user: inout User) throws {
    guard let points <= user.points else {
        throw PointsShortError()
    }
    user.points -= points
}

var user: User = .init(...)
try consumePoints(100, of: &user)
```
`inout`引数は、引数に渡された値が関数の中で変更された場合、呼び出し元へ反映するというもの。
一歩間違えるとValue Semanticsを破壊する危険なものであるため、`inout`や`&`を記述して値型にとって不自然な挙動を引き起こすための明示的な意思表示が必要であったり、`inout`な変数やプロパティを作ったり`inout`な戻り値を作ったりすることはできないように設計されている。

### `mutating func`
関数ではなく`User`のメソッドとして実装する場合は以下のようになる。
```swift
extension User {
    mutating func consumePoints(_ points: Int) throws {
        guard points <= self.points else {
            throw PointsShortError()
        }
        self.points -= points
    }
}

var user: User = .init(...)
try user.consumePoints(100)
```
`mutating`を付与することは暗黙の引数`self`に`inout`を付けるのと同じ意味。

### Conputed Propertyを介した変更
SwiftではComputed PropertyをStored Propertyと同じように扱うことができる。Computed Propertyを介して状態を変更することも可能。
```swift
struct Group {
    var owner: User {
        get { members[0] }
        set { members[0] = newValue }
    }
    ...
}

group.owner.points = 0 // ✅
```
Computed Propertyはメソッドのようなものであるため、`owner`がComputed Propertyではなくメソッドだと考える。
この時`User`が値型の場合、`owner`メソッドの戻り値に対して変更を加えようとするとコンパイルエラーになる。
これは`group.owner()`の戻り値はreturnされる際にコピーされるものであり、コピーされた`User`インスタンスは一時的にメモリ上に存在するだけであるため変更を加えてもその結果は破棄されてしまう。
こういった無意味なケースをSwiftコンパイラはコンパイルエラーとして検出する。

しかし`owner`がComputed Propertyの場合は以下のような手順で処理が行われる。
1. `group.owner`の`get`を用いて`User`インスタンスのコピーが返される
2. そのコピーの`points`が0に変更される
3. 変更を加えられたコピーが`group.owner`の`set`の`newValue`として渡され、変更が`group`に反映される

また、Computed Propertyに加えて`subscript`を介した変更も同じように動作する。
```swift
group.members[i].points = 0 // ✅
```
これも
1. `subscript`の`get`
1. `points`の変更
1. `subscript`の`set`

という順で実行される。
また、Computed Propertyや`subscript`を介した変更は、`inout`引数や`mutating func`と組み合わせることもできる。
```swift
// inout 引数との組み合わせ
try consumePoints(100, of: &group.owner) // ✅

// mutating func との組み合わせ
try group.owner.consumePoints(100) // ✅
```

### 高階関数と`inout`引数の組み合わせ
複数の`User`、`[User]`について考える。
もし`User`がミュータブルクラスであるなら単純に`for-in`ループで値を1つずつ取り出して変更を加えるだけだが、値型の場合は`for-in`ループのループ変数はデフォルトで定数であるためコンパイルエラーになってしまう。
また、ループ変数を変数にした場合コンパイルは通るようになるが、1つのデータをコピーしてループ変数に格納しているため、コピーへの変更がオリジナルに反映されない。

こういったケースの対処法としては、
- インデックスを介して状態を更新する
- `inout`引数を持つ`mutating func`を用いる

という方法がある。

```swift
var users: [User] = ...

// for-inループ
for user in users {
    user.points += 100 // コンパイルエラー
}

// for-inループのループ変数を変数に
for var user in users {
    user.points += 100 // コンパイルは通るが、実行しても何も起きない
}

// インデックスを介して状態を変更する
for i in users.indices {
    users[i].points += 100 // コンパイルが通り、変更も反映される
}

// inout引数とmutating funcを用いる
extension MutableCollection {
    mutating func modifyEach(_ body: (inout Element) -> Void) {
        for i in indices {
            body(&self[i])
        }
    }
}
users.modifyEach { user in
    user.points += 100 // コンパイルが通り、変更も反映される
}
```

また、この`modifyEach`相当のことは将来的には`for-in`ループと`inout`の組み合わせでできるようになる可能性がある。
```swift
// 将来的には以下の形になるかもしれない
for inout user in users {
    user.points += 100
}
```

## 値型とミュータブルクラスの使い分け
Swiftにおいてイミュータブルクラスを作るべきケースはほとんど存在しない。

まず(Value Semanticsを持つ普通の)値型で実装し、それでうまくいかない場合にミュータブルクラス(Reference Semantics)を検討するという方法がおすすめ。
そうすることでValue Semanticsを持たない型の利用を最小限に留められ、「意図しない変更」や「整合性の破壊」などの発生機会を最小化し問題のコントロールが容易になる。

Value Semanticsを持つ世界・持たない世界を分離しておくことが重要。