話題の記事

WWDC 2015 で Swift 2 が発表されました。

2015.06.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Swift 2が発表されました

2015/6/8 アメリカはサンフランシスコで開催された WWDC2015 にてプログラミング言語Swiftの新バージョンであるSwift 2が発表されました。

私もTwitterのタイムラインを尻目に生中継を視聴していましたが、その中でとりわけ注目を集めていたのはなんといってもSwiftがオープンソース化し、将来的にはLinuxの上でも動くようになるという発表でした。

Appleの公式ブログによればSwiftは標準ライブラリ、コンパイラも合わせて今年の遅くにオープンソース化されるとのことです。OSSコミュニティからのコントリビューションも積極的に取り入れられるとのことで、バグの原因もOSSコミュニティの寄与によってパッチ等があてられて、目に見える形で言語開発が進むことが期待されます。

ドキュメント

Pre-release版としてドキュメントがWWDC2015のKeynote直後から公開されています。

中でもThe Swift Standard Libraryのドキュメントはこれまでとは異なり、Swiftの標準APIの全体を網羅した内容になっています。これまではソースコードのコメントのみがApple公式による手がかりだったAPIもより扱うための敷居が低くなりそうです。

言語の新しい特徴

Swift 2で発表された言語の新しい特徴は以下のようになっています。

throws-catchベースのエラーハンドリング

ErrorTypeプロトコルとして既存の型を準拠させることでエラーとでき、関数・メソッドの引数宣言の後に続けて throwsと書くことでエラーを起こしうる関数・メソッドであることを明示的にできます。

enum TextError: ErrorType {
    case LongLength
    case ShortLength
}

func validateText(text: String) throws {
    if text.utf8.count > 10 {
        throw TextError.LongLength
    } else if text.utf8.count < 4 {
        throw TextError.ShortLength
    }
}

throwsがつけられた関数・メソッドはdo-catch文でエラーハンドリングできます。

func checkTextField() {
    do {
        try validateText("aaaaa")
    } catch TextError.LongLength {
        print("long", appendNewline: true)
    } catch TextError.ShortLength {
        print("short", appendNewline: true)
    } catch _ {
        assert(false)
    }
}

do-catch文はswitch-case文と同様に網羅的であることが求められます。上記例ではthrowsと宣言せずにエラーを出さないことを明示的にしている関数の中でdo-catch文をあつかっています。

do-catch文内ではエラーを出しうるvalidateText関数を処理していますが、エラーを網羅的に処理しないとコンパイラがその旨を知らせてくれます。

個人的にはインターフェイスとしてthrowsを行うメソッドをつくるよりも既存のOSSに見られるResult型のようなエラーを内包した返り値にわたした方がいいような感覚はありますが、既存のシステムを扱う際にthrows-catchベースのハンドリングの方が都合がいいということでしょうか。

スコープ外に抜けた際にコールされるdefer文

defer文は関数・メソッドやその他制御構造でできるスコープを抜ける時に実行される便利な構文です。ファイルを開いた時スコープを抜けた際の後始末等に有用です。

func manageSignal() {
    print("Blue") // -- (1)
    defer {
        print("Red") 
    } // -- (2)
    print("Yellow") // -- (3)
}
// -- (4)

この例では各処理について

-- (1) を抜けた直後で文字列Blueが出力され

-- (2) を抜けた直後ではなにもおこらず

-- (3) を抜けた直後で文字列Yellowが出力され

ますが、関数のスコープをぬけた -- (4) ではdefer文の中身が実行され、文字列Redが出力されます。

複数のdefer文があるときにはdefer文が宣言された順序とは逆にdefer文の中身が実行されていきます。

func printSignal() {
    defer { print("Blue") }
    defer { print("Red") }
    defer { print("Yellow") }
}
printSignal() // Yellow Red Blue

OSバージョンに応じたAPI利用可否の判定

@availableディレクティブで宣言されたOSバージョン情報については、ターゲットビルド情報に合わせた分岐不要の判定と、実行時のAPI利用可否を#available条件式を用いて判定出来るようになりました。例えばif文で#available条件式を用いる場合は以下のようになります。

extension UINavigationController {
    func showViewControllerForCompatible(vc: UIViewController, sender: AnyObject?) {
        if #available(iOS 8.0, *) {
            showViewController(vc, sender: sender)
        } else {
            pushViewController(vc, animated: true)
        }
    }
}

この例はUINavigationControllerに対してOS互換な遷移機能を追加したものです、実行時のOSバージョンに応じた処理分岐が行われます。また、ターゲットビルド設定がiOS8より大きい時には判定を行う必要がないというエラーがでます。

プロトコルに対するエクステンション

プロトコルに対してデフォルト実装を行いたい場合や、デフォルト実装を利用できるプロトコルに制約を付けたい場合にはプロトコルに対するエクステンションを用いれます。

プロトコルへのデフォルト実装

既存のプロトコルへの実装をエクステンションでは行えます。

extension SequenceType {
    func any(cond: Generator.Element -> BooleanType) -> Bool {
        return reduce(false) { acc, elem in
            acc || cond(elem).boolValue
        }
    }
}

この例は標準APIのSequenceTypeに対してエクステンションを宣言し、少なくともひとつの要素が条件を満たすか判定を行うメソッドを追加しています。プロトコルに対してはこのエクステンションを用いてデフォルト実装を行うことも出来ます。

エクステンションを用いるプロトコルに対する制約

デフォルト実装を利用できるプロトコルに制約を付けたい場合も、エクステンションを用いて実現できます。

extension SequenceType where Generator.Element: Equatable {
    func intersect(other: Self) -> [Generator.Element] {
        return filter { elem in other.contains(elem) }
    }
}

この例も標準APIのSequenceTypeに対してエクステンションを宣言していますが、その要素に対してEquatableプロトコルに準拠しているべきだという制約を設けています。whereの後の制約の設けかたは型引数に対するそれと同様です。

このように制約のついたプロトコルへのエクステンションで定義されたメソッドについては、準拠する型がその制約をみたしていなければエラーとなります。

[1, 2, 3].intersect([2, 4, 5]) // -- (1)

struct Fruit {
    let name: String
}
let fruits: [Fruit] = [Fruit(name: "orange"), Fruit(name: "apple")]
fruits.intersect(fruits) // -- (2)

例えばこのコードでは(1)の[1, 2, 3]はSequenceTypeに準拠したArray型であり、さらにその要素はEquatableプロトコルに準拠したInt型であるためにエラーとはなりません。

一方(2)の[Fruit(name: "orange"), Fruit(name: "apple")]はSequenceTypeに準拠したArray型ではありますが、Fruit型がEquatableプロトコルに準拠していないためにエラーとなります。エラーを取り除くためにはFruit型がEquatableプロトコルに準拠する必要があります。

従来の標準APIには型引数でCollectionType, SequenceTypeに準拠した引数や返り値を用いたグローバル関数が多く定義されていましたが、Swift 2からはそれらはプロトコルに対するエクステンションで実装されています。

また、これらの集合を表すようなプロトコルの要素への制約は制約のついたプロトコルに対するエクステンションで実装されています。

このようなプロトコルへの自由度の追加でプログラムの設計方針に大きな変更がもたらされると期待されます。実際、Protocol Oriented Programming in Swift と題したセッションもWWDC2015では開催されている模様です。

@testableディレクティブ

自作フレームワークのテストを行いたいときには@testableをフレームワークのインポート文の前に付けることでフレームワークでinternalとして宣言されているあらゆるクラス、メソッド、関数…etcをpublicとして扱えます。

テストターゲットでMyFrameworkという名前のついたフレームワークをinternalの部分までテストする場合は以下のように宣言を行います。

@testable import MyFramework

今までのSwiftの仕様がおかしいといわざるを得ませんが、これでテストの為にわざわざpublicにする必要がなくなり、より自然な設計を実現できると期待できます。

スコープ外への脱出を明示的に行うguard文

if文でメソッドや関数からの脱出を行ってそれ以降の処理を行わない場合がありますが、このような定形処理に対しては新しく追加されたguard文が役にたつかもしれません。

guard文はif文とほとんど同じような記法で記述が可能ですが、else文が必須であることと、else文の中では必ずそれ以降の処理が行われないような文を記述し、スコープ外への脱出を明示的に行う必要があります。

func exitFunc() {
    guard let someInt = Int("aaa") else {
        return
    }
    print(someInt)
}

例えばここではif let文と同じような書き方でInt?を返すInt("aaa")の中身があるかチェックし、あればsomeIntにバインドします。

if文と異なるのはバインドされたsomeIntがguard文のスコープを抜けた後でも使えることと、else文でもしも脱出を行うような処理を記述しない場合(ここではreturn)エラーとなることです。

途中で抜けるような処理に関してはコードにリファクタリングの余地がある目安とも考えられるのですが、スコープ外に抜けるべきであることをエラーを出してより明示的に出来るためにリファクタリングの途中段階としてお世話になる機会は多そうです。

パターンマッチングのif,while,for-inへの追加

switch文で使えたパターンマッチングがif, while, for-inの条件式でも使えるようになりました。

let someInt: Int? = 42
if case .Some(let x) = someInt {
    print(x)
}

ここではsomeIntがマッチしたい変数、case文の直後にパターンを記述しています。パターンが一致するような変数だったときにはif分の中のprintが走り、出力が行われます。

その他while, guard文等の条件式を適用できる部分には同様の方法でcaseを用いてパターンマッチできます。

またfor-in文に対しても以下のようにパターンマッチできます。

let points = [(1, 3), (3, 3), (4, 0)]
for case let (x, y) in points where x > 2 && y > 2 {
    print("(x), (y)")
}

ここではタプルで定義された点の配列を走査して x > 2, y > 2となるようなx-y平面上の座標を出力しています。

letの推奨

varを用いて変数として宣言されている文についても、その変数が変更されないのであれば、letを用いて定数として宣言するように警告が出るようになりました。

更に、一度も用いられない定数は_(アンダースコア)で宣言するように警告が出るようになっています。

Enumへの複数型引数サポート

複数の型引数を用いるようなEnumは従来のSwiftではコンパイラエラーが出るようになっていました。そのため、class Box<T>等を挟んで複数型引数とEnumを扱っていました。Swift 2ではこの問題が解消され、下記のような書き方ができるようになりました。

enum Either<A, B> {
    case Left(A)
    case Right(B)
}

他にも紹介しきれない変更点が多数ありますが、際立ったもののみ今回はピックアップしました。

今後に向けて

オープンソース化し、Linuxへの展開も目指していくということで今までOSX/iOSなどのクライアントサイド言語だったSwiftがサーバーサイドにデプロイされ、サーバーサイドフレームワークの一部として動く日もそう遠くないのかもしれません。

サーバーサイドでは様々な条件を加味する必要が言語選定時には出てきますが、OSS化によるより開かれた議論のもとにさらされることで、より多方面に適用できる言語になることを楽しみにしています。