166
74

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Swift 5.2の新機能

Last updated at Posted at 2020-03-25

本日(2020年3月25日、現地時間では24日) Swift 5.2 がリリースされました

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

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

SE-0249: Key Path Expressions as Functions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

structclass などの nominal type を関数のように使えるようにする変更です。

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

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

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

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

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

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

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

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

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

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

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

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

なお、ここでは一次関数型を例に挙げましたが、 Proposal にはその他にも callAsFunction の使いどころが色々挙げられています

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

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

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

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

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

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

// 関数のように使う
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 が書かれているので、 callAsFunction を持つインスタンスの暗黙的変換は将来的にサポートされるかもしれません。ただ、そうすると SE-0249 は KeyPathcallAsFunction を持たせるだけで良かったんじゃないかとも思います( Key Path 式だけでなく KeyPath インスタンスも関数型に暗黙的変換できるようになるのと、 KeyPath インスタンスを関数のように使えるようになるという違いがあるので、それらが望ましいかどうかの検討は必要ですが)。

まとめ

  • Swift 5.2 は開発者の UX 改善が中心で言語仕様の変更は少ない。
  • 言語仕様の変更に関する Proposal は SE-0249SE-0253 の二つ。
  • 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$ の形で実装することが一般的です。

166
74
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
166
74

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?