Swift 5.2の新機能

本日（2020年3月25日、現地時間では24日） [Swift 5.2 がリリースされました](https://swift.org/blog/swift-5-2-released/)。

Swift 5.2 がフォーカスしているのは、コード補完やエラーメッセージの改善など、開発者の UX 改善で、言語仕様に加えられた変更は多くありません。 UX の改善点については[公式ブログが詳しく解説している](https://swift.org/blog/swift-5-2-released/)ので、本投稿では **Swift 5.2 における言語仕様の変更点について紹介します。**

Swift 5.2 で言語について加えられた変更は次の二つです。

- [SE-0249: Key Path Expressions as Functions](https://github.com/apple/swift-evolution/blob/master/proposals/0249-key-path-literal-function-expressions.md)
- [SE-0253: Callable values of user-defined nominal types](https://github.com/apple/swift-evolution/blob/master/proposals/0253-callable.md)

## SE-0249: Key Path Expressions as Functions

Key Path 式を関数として渡せるようにする変更です。

たとえば、 `User` の `Array` から ID の `Array` を作りたいときには、これまでは次のように書けました。

```swift
// BEFORE
let users: [User] = ...
let ids: [String] = users.map { $0.id }
```

Key Path 式を関数として渡せると、次のように書けるようになります。

```swift
// AFTER
let users: [User] = ...
let ids: [String] = users.map(\.id)
```

また、関数型の変数に Key Path 式を代入することもできるようになりました。

```swift
let getUserId: (User) -> (String) = \.id
```

しかし、これらは暗黙的変換であり、 Key Path 式自体が関数にように使えるわけではありません。

```swift
let id: String = \.id(user) // ⛔ NG
```

これを Key Path 式でやりたければ次のように書く必要があります。

```swift
let id: String = user[keyPath: \.id] // ✅ OK
```

なお、注意が必要なのは、 SE-0249 が対象としているのは Key Path 式だけで、 `KeyPath` インスタンスを関数型に暗黙的変換できるわけではないことです。たとえば、↓はできません。

```swift
let keyPath: KeyPath<User, String> = \.id
let ids: [String] = users.map(keyPath) // ⛔ NG
```

## SE-0253: Callable values of user-defined nominal types

`struct` や `class` などの nominal type を関数のように使えるようにする変更です。

たとえば、一次関数 $ax + b$ を表す型を作りたいとします。一次関数を関数ではなく nominal type として実装したい理由の例としては、 `a` や `b` をプロパティで持つようにして、二直線の交点を求めるメソッドを作ったりしたいなどが考えられます[^1]。しかし、一次関数型インスタンスは `f(5)` のように関数として使いたい気持ちにもなります。これまではそれらを両立できませんでした。

一次関数を `Linear` という型で表すとします。これまでは次のように書く必要がありました。

```swift
// BEFORE
let f = Linear(a: 2, b: 3) // 一次関数 2x + 3
print(f.evaluate(5))       // x = 5 を代入
```

Swift 5.2 からは、関数のように呼び出すことが可能になります。

```swift
// AFTER
let f = Linear(a: 2, b: 3) // 一次関数 2x + 3
print(f(5))                // x = 5 を代入
```

上記のように `Linear` を関数として使えるようにするには、 `Linear` に `callAsFunction` という名前のメソッドを実装します。

```swift
extension Linear {
    func callAsFunction(_ x: Double) -> Double {
        a * x + b
    }
}
```

`callAsFunction` メソッドを持つ型のインスタンスは何でも、関数のように呼び出せます。

しかし、 `callAsFunction` を持つインスタンスを関数型に暗黙的変換することはできないので注意が必要です。

```swift
let g: (Double) -> Double = f // ⛔ NG
```

関数型に変換するには明示的に `callAsFunction` メソッドを指定します。

```swift
let g: (Double) -> Double = f.callAsFunction // ✅ OK
```

なお、ここでは一次関数型を例に挙げましたが、 Proposal にはその他にも `callAsFunction` の使いどころが[色々挙げられています](https://github.com/apple/swift-evolution/blob/master/proposals/0253-callable.md#motivation)。

## 個人的に違和感を感じるところ

SE-0249 と SE-0253 は整合性のバランスが微妙に思えます。

関数のように使えない Key Path 式は関数型に暗黙的変換できるのに、

```swift
// 関数のように使う
//let id: String = \.id(user)            // ⛔ NG
let id: String = user[keyPath: \.id]     // ✅ OK

// 関数型へ変換
let getUserId: (User) -> (String) = \.id // ✅ OK
```

関数のように使える `callAsFunction` を持つインスタンスは関数型に暗黙的変換できません。

```swift
// 関数のように使う
print(f(5))                                  // ✅ OK

// 関数型へ変換
let g: (Double) -> Double = f                // ⛔ NG
let g: (Double) -> Double = f.callAsFunction // ✅ OK
```

SE-0253 の Future directions には [Implicit conversions to function](https://github.com/apple/swift-evolution/blob/master/proposals/0253-callable.md#implicit-conversions-to-function) が書かれているので、 `callAsFunction` を持つインスタンスの暗黙的変換は将来的にサポートされるかもしれません。ただ、そうすると SE-0249 は `KeyPath` に `callAsFunction` を持たせるだけで良かったんじゃないかとも思います（ Key Path 式だけでなく `KeyPath` インスタンスも関数型に暗黙的変換できるようになるのと、 `KeyPath` インスタンスを関数のように使えるようになるという違いがあるので、それらが望ましいかどうかの検討は必要ですが）。

## まとめ

- Swift 5.2 は開発者の UX 改善が中心で言語仕様の変更は少ない。
- 言語仕様の変更に関する Proposal は [SE-0249](https://github.com/apple/swift-evolution/blob/master/proposals/0249-key-path-literal-function-expressions.md) と [SE-0253](https://github.com/apple/swift-evolution/blob/master/proposals/0253-callable.md) の二つ。
- SE-0249 で Key Path 式が関数に暗黙的変換できるようになる。
  - 例: BEFORE `users.map { $0.id }` → AFTER `users.map(\.id)`
- SE-0253 で `callAsFunction` メソッドを持つ型のインスタンスを関数のように使えるようになる。
  - 例: BEFORE `f.evaluate(5)` → AFTER `f(5)`
- Swift 5.2 時点では、関数のように使えない Key Path 式は関数型に暗黙的変換できるのに、関数のように使える `callAsFunction` を持つインスタンスは関数型に暗黙的変換できないことに筆者は違和感を感じる。

[^1]: 実際に直線を表す型を作るときには、 $y$ 軸に平行な直線も表現できるように $ax + by + c = 0$ の形で実装することが一般的です。