What’s new in Swift 5

この記事はSwift Advent Calendar 2018の18日目の記事です。今年はアドベントカレンダー書く余裕がないなぁと思っていたら、書く余裕ができたので書きました。

また、同じ内容をHakata.Swift #5で発表しました。登壇資料の代わりとさせていただきます。

Swift 5 Final Branch

Swift5は11/16にfinal branchが切られました。 swift-5.0-branch をcheckoutすることでSwift5の世界を一足先に体験することができます。

November 16, 2018 (final branching): The swift-5.0-branch will have changes merged from master one last time. After the final branch date there will be a “bake” period in which only select, critical fixes will go into the release (via pull requests).

Swift 5で目指していたものは、以下の記事で読むことができます。

執筆現在では、いつリリースされるかは発表されていません。

ABI Stability

Swift 5でリリース要件としていたABI Stabilityは執筆現在、Documentationを除いて実装が完了しています。

ABIの安定化とは、簡単に述べておくと、アプリ本体と、Frameworkなどのバイナリで、使用しているSwiftのバージョンが異なっていても、動作させることができるというものです。

Swift 4で目標としていたところから遅延してのリリースですが、いよいよABIの安定化がSwiftにやってくることになります。

ABIの安定化の進捗状況はこちらで確認することができます。

Swift 5で遊んでみる

Swift5でちょっと遊んでみたいな、と思ったけどどうすれば良いかわからない方に遊び方を説明しておきます。

Xcodeにtoolchainを入れる

Swift5.0 developmentのtoolchainはswift.orgからダウンロードすることができます。

pkgを解凍すると、Xcode > Toolchainsから選択できるようになります。

ただし、playgroudでは”Broken pipe”や”Connection refused”でコンソールに結果は出力されません。syntax higlightやcompletionは有効です。

swiftを直接ビルドしてtoolchainを生成するという手法もありますが、非常に時間がかかるのでおすすめしません。

やってみたい方はこちらを参照してください。

Swift-Playground

岸川さんがホスティングしているSwift Playgroundです。任意のSwiftのバージョンでSwiftを実行できます。

DiscordのSwiftbot

swift-developers-japanのDiscordには有志によるswiftbotが設置されています。botに話かけるだけでSwiftを実行できます。他の人も実行結果を見ることができるので、コードの共有がしやすく便利です。

Swift-developers-japanへの参加はこちらを参照してください。

19 Implemented Proposals

ここからは、Swift 5に導入される19のProposalについて、簡単にポイントを解説します。導入の背景や、議論の経緯は、実際のProposalおよびForumを参照してください。

※一部のproposalで自分の理解が及んでないものがあります。dynamic callableのproposalです。後日加筆したいと考えています。

[SE-0192] Handling Future Enum Cases

@unknown という属性が追加されます。これはenumの default: にのみ適応できる属性です。

enumでdefaultケースを使うことの欠点は、ケース漏れが生じてもwarningが発生しないことです。これは、enumのケースが増えた時に、適切なハンドリングがされず、バグを引き起こす原因になりかねません。これを回避するために、@unknown属性によってwarningを発生させ、将来のcaseの追加に備えます。

[SE-0200] Enhancing String Literals Delimiters to Support Raw Text

String LiteralのDelimiterに # が使えるようになりました。これにより、次のように”や\など、これまでエスケープが必要だった文字のエスケープが不要になります。

 let rain = #”The “rain” in “Spain” falls mainly on the Spaniards.”#
print(rain)
// The “rain” in “Spain” falls mainly on the Spaniards.
 let keypaths = #”Swift keypaths such as \Person.name hold uninvoked references to properties.”#
// Swift keypaths such as \Person.name hold uninvoked references to properties.

# で括られたString Literalの中でString Interpolationを適応するには、 `\#()` で囲ってあげます。

let answer = 42
let dontpanic = #”The answer to life, the universe, and everything is \#(answer).”#
// The answer to life, the universe, and everything is 42.

# で囲まれたString Literal で、 # を出力してあげるには、全体を ## で囲みます。

 let str = ##”My dog said “woof”#gooddog”##
print(str)
// My dog said “woof”#gooddog

Multiline Stringもこの通りです。

 let answer = 42
let multiline = #”””
The answer to life,
the universe,
and everything is \#(answer).
“””#
print(multiline)

/*
The answer to life,
the universe,
and everything is 42.
*/

個人的に一番うれしいのは、regexにescapeがいらなくなったことです。

let regex2 = #”\\[A-Z]+[A-Za-z]+\.[a-z]+”#
print(regex2)
// \\[A-Z]+[A-Za-z]+\.[a-z]+

#の挙動については、慣れるまでに少し時間がかかりそうですが、文字列を見通しよく扱えるようになりそうです。

[SE-0211] Add Unicode Properties to Unicode.Scalar

Unicode.Scalarにプロパティが追加されました。 isEmoji というフラグが追加されているのは気になります。

[SE-0221] Character Properties

Unicode.Scalarと同じように、Characterに数々のpropertyが追加されました。

個々の紹介は控えますが、 isASCIIisWhitespace などの判定フラグ、 upperCased() などのcase conversionのメソッドが追加されています。

[SE-0213] Literal initialization via coercion

coercionは「強制」という意味だと思ってください。

これまでは、Literalは対応する型が優先され、例えば次のリテラルはInt型と見なされていました。

print(UInt(0xffff_ffff_ffff_ffff))
// <stdin>:6:12: error: integer literal '18446744073709551615' overflows when stored into 'Int'

まずInt型への代入が行われるため、上記の例ではIntの上限値を超えてOverflowします。

これを回避するためには、LiteralをUIntでキャストします。

print(UInt(0xffff_ffff_ffff_ffff as UInt))
// 18446744073709551615

Swift 5では、initializeの型が優先されます。先ほどErrorになった例では、次のようにLiteralがUIntで判定されるため、as UIntによるキャストが不要になります。

print(UInt(0xffff_ffff_ffff_ffff))
// 18446744073709551615

[SE-0214] Renaming the `DictionaryLiteral` type to `KeyValuePairs`

DictionaryLiteralKeyValuePairs にrenameされました。

[SE-235] Add Result to the Standard Library

(追記:この記事を公開してから、ResultがSwift5に取り込まれることになりました。)

Resultはエラーハンドリングに用いられる型です。これまでSwiftでResult型を用いるには、antitypical/Resultが代表されるように、サードパーティのライブラリを用いることになっていましたが、このたび標準ライブラリに含まれることになります。

標準ライブラリで取り込まれるResultについては、次の記事が詳しいので、ここでは割愛します。

public enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
public func map<NewSuccess>(
_ transform: (Success) -> NewSuccess
) -> Result<NewSuccess, Failure> {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return .failure(transform(failure))
}
}
public func mapError<NewFailure>(
_ transform: (Failure) -> NewFailure
) -> Result<Success, NewFailure> {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return .failure(transform(failure))
}
}
public func flatMap<NewSuccess>(
_ transform: (Success) -> Result<NewSuccess, Failure>
) -> Result<NewSuccess, Failure> {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return .failure(transform(failure))
}
}
public func flatMapError<NewFailure>(
_ transform: (Failure) -> Result<Success, NewFailure>
) -> Result<Success, NewFailure> {
switch self {
case let .success(success):
return .success(success)
case let .failure(failure):
return .failure(transform(failure))
}
}

extension Result where Failure == Swift.Error {
@_transparent
public init(catching body: () throws -> Success) {
do {
self = .success(try body())
} catch {
self = .failure(error)
}
}
}
extension Result: Equatable where Success: Equatable, Failure: Equatable { }
extension Result: Hashable where Success: Hashable, Failure: Hashable { }

[SE-0215] Conform Never to Equatable and Hashable

NeverEquatableHashable に適合しました。

 extension Never: Error {}
extension Never: Equatable {}
extension Never: Comparable {
public static func < (lhs: Never, rhs: Never) -> Bool {
switch (lhs, rhs) {
}
}
}
extension Never: Hashable {}

この変更は先ほど紹介した Result で恩恵を受けることができます。

 let foo = Result<Int, Never>.success(1)
let bar = Result<Int, Never>.success(1)
print(foo == bar)

Resultは2つの型を受けますが、`success` または failureどちらか一方の型しかとらないという宣言の仕方をする場合、これまでは NoError のように独自型を作り、 Equatable Hashable に準拠させる必要がありました。

これを Never で行うことができれば、独自に型を作る必要はなくなります。そのために、最後の行のように、比較演算を行うためには、IntがEquatableに準拠しているだけでは不十分で、 NeverEquatable に準拠している必要があります。

[SE-0216] Introduce user-defined dynamically “callable” types

Swift4.2で追加されたdynamicMemberLookupへのフォローアップです。新たに @dynamicCallable 属性が追加されました。

(もう少し理解を深めてから加筆します。)

PythonKitというOSSで使われています。

[SE-0218] Introduce compactMapValues to Dictionary

DictionaryのValueの型変換や、nilの除去が行えます。

let times = [
"Hudson": "38",
"Clarke": "42",
"Robinson": "35",
"Hartis": "DNF"
]
let finishers1 = times.compactMapValues { Int($0) }
let finishers2 = times.compactMapValues(Int.init)
print(finishers1) // ["Hudson": 38, "Clarke": 42, "Robinson": 35]
print(finishers2) // ["Hudson": 38, "Robinson": 35, "Clarke": 42]
 let people = [
"Paul": 38,
"Sophie": 8,
"Charlotte": 5,
"William": nil
]

let knownAges = people.compactMapValues { $0 }
print(knownAges) // ["Paul": 38, "Sophie": 8, "Carlotte": 5]

こちらは某作者の方が説明書を書いています。こちらもよかったら読んでください。

また、この作者は、contributionまでの経緯をtry! Swift NYC 2018で発表しています。

[SE-0219] Package Manager Dependency Mirroring

SwiftPMにDependency Mirroringを導入します。

Packageが依存するライブラリのoriginalのURLに加え、ミラーリングサイトのURLを追加できるようになります。

$ swift package config set-mirror \
--package-url <original URL> \
--mirror-url <mirror URL>

# Example:

$ swift package config set-mirror \
--package-url https://github.com/Core/libCore.git \
--mirror-url https://mygithub.com/myOrg/libCore.git

[SE-0220] count(where:)

次のように、where句の条件に合う要素の数をcountします。

let scores = [100, 80, 85]
let passCount = scores.count { $0 >= 85 }
print(passCount) // 2

[SE-0224] Support ‘less than’ operator in compilation conditions

swift、およびSwift compilerのバージョン判定において、 < の演算子が追加されました。

 
#if swift(<5.1)
// Swiftが5.1未満のときに呼ばれる処理
#endif

#if compiler(<5.1)
// Swift compilerが5.1未満のときに呼ばれる処理
#endif

[SE-0225] Adding isMultiple to BinaryInteger

BinaryIntegerの倍数判定メソッドが追加されました。

 if 4.isMultiple(of: 2) {
print("isEven")
} else {
print("isOdd")
}

[SE-0227] Identity Keypath

変数の.selfが呼べることのアナロジーとして、型自身のKeypathの取得が行えるようになりました。これにより、次のような表現ができます。

 var x = 1
x.self = 2
print(x.self) // prints 2

let id = \Int.self

x[keyPath: id] = 3
print(x[keyPath: id]) // prints 3

[SE-0228] Fix ExpressibleByStringInterpolation

Swift 3でdeprecatedになった ExpressibleByStringInterpolation の改善案です。String Interpolationの appendInterpolation を拡張すると、String Interpolationに指定する引数を拡張したり、出力をコントロールできます。

import Foundation
public extension String.StringInterpolation {
mutating func appendInterpolation(_ value: Date, _ formatter: DateFormatter) {
appendLiteral(formatter.string(from: value))
}
}
public extension DateFormatter {
static func format(date: Style, time: Style) -> DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale.current
(formatter.dateStyle, formatter.timeStyle) = (date, time)
return formatter
}
}
let formatter = DateFormatter.format(date: .none, time: .long)
print("\(Date(), formatter)") // 3:11:18 PM GMT+9

初見でいまいち使い方がわからなかったところを教えてもらいました。

実用例はこのrepositoryが詳しいです。

[SE-0230] Flatten nested optionals resulting from ‘try?’

try?によるOptionalのnestを防ぎます。

これまではtry?によって、OptionalはOptional<Optional>とネストしていました。

 let q = try? nonOptionalValue() // MyType?
let r = try? optionalValue(0 // MyType??

これは、Optional Bindingによる値の取り出しを複雑にします。

Swift 5では次のようにOptionalのネストが平坦化されます。

let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
// Swift4: [String: Any]??
// Swift5: [String: Any]?
code:flattenOptionalTry2.swift
let url = URL(string: "https://swift.org")!
let fileContents = try? String(contentsOf: url)
// Swift4: String?
// Swift5: String?
code:flattenOptionalTry3.swift
func doubleOptionalInt() throws -> Int?? {
return 3
}

let x = try? doubleOptionalInt()
// Swift4: Int???
// Swift5: Int??

[SE-0232] Remove Some Customization Points from the Standard Library’s Collection Hierarchy

次のCustomization Pointsが削除されます。
 Sequence.map , Sequence.filter , Sequence.forEach , Collection.first , Collection.prefix(upTo:) , Collection.prefix(through:) , Collection.suffix(from:) , BidirectionalCollection.last

ただし、デフォルト実装は残るため、挙動には影響しませんが、カスタム実装ができなくなります。

[SE-0233] Make Numeric Refine a new AdditiveArithmetic Protocol

Numeric プロトコルの数学的な間違いを正すために、`AdditiveArithmetic` というプロトコルを導入し、 Numeric プロトコルをrefineしました。

具体的には + , -AdditiveArithmetic に宣言され、乗算と 0 の定義に関しては、 Numeric に宣言されています。

[SE-0234] Remove Sequence.SubSequence

SequenceのassociatedTypeであるSubsequenceの型消去を解消しました。

 func dropFirst(_:) -> SubSequence
func dropLast(_:) -> SubSequence
func drop(while:) -> SubSequence
func prefix(_:) -> SubSequence
func prefix(while:) -> SubSequence
func suffix(_:) -> SubSequence
func split(separator:) -> [SubSequence]

上記のメソッドにはデフォルト実装として、AnySquence<Element>を返す実装が行われており、これは、内部に存在している `DropFirstSequence` , `DropWhileSequence`, `PrefixSequence`の型消去を行う格好となっています。

この実装により、パフォーマンスが悪化したり、`Collection`の Sequence へのConditional Conformanceを妨げています。

これを改善するために、次のように具体的な型を返すようにします。

extension Sequence {
public func dropFirst(_ k: Int = 1) -> DropFirstSequence<Self>
public func dropLast(_ k: Int = 1) -> [Element]
public func drop(while predicate: (Element) throws -> Bool) rethrows -> DropWhileSequence<Self>
public func prefix(_ maxLength: Int) -> PrefixSequence<Self>
public func prefix(while predicate: (Element) throws -> Bool) rethrows -> [Element]
public func suffix(_ maxLength: Int) -> [Element]
public func split(maxSplits: Int = Int.max, omittingEmptySubsequences: Bool = true, whereSeparator isSeparator: (Element) throws -> Bool) rethrows -> [ArraySlice<Element>]

Swift 5 Coming Soon!

というわけで、Swift 5がやってきます。

ABIの安定化によって、これまでのようにXcodeに追従してすぐに言語のバージョンをあげたり、Libraryのリビルドをし直すようなことは少なくなるように思いますが、この先、async/await の導入が行われる見込みだったり、今後もSwiftはどんどん進化を続けていきます。iOS/macOSのアプリ開発では今後も主要な言語として利用されます。これからもより良く進化していくことを強く望んでいます。