2014年6月3日火曜日

iOS8 で採用されるSwift言語オーバービュー

 iOS8より新しくプログラミング言語にSwiftが採用されることがWWDCのKeynoteにて発表されました。リファレンスを読んだ印象では、Haskellなどの関数型言語の影響をたぶんに受けているように感じます。
たしかに今までAppleがObjective-Cに新しくBlock構文を採用してクロージャをもりもりかけるようにしたり、コンパイラの改良を行って型チェックをより賢くして来たという経緯もふまえてみると、自然な流れにも思えます。

0.はじめに
Swift is a new programming language for iOS and OS X apps that builds on the best of C and Objective-C, without the constraints of C compatibility. Swift adopts safe programming patterns and adds modern features to make programming easier, more flexible, and more fun. Swift’s clean slate, backed by the mature and much-loved Cocoa and Cocoa Touch frameworks, is an opportunity to reimagine how software development works.
SwiftはCやObjective-Cをふんだんに利用しているiOSやOS Xアプリのための、新しいプログラミング言語で、C言語との互換性はありません。 Swiftは安全なプログラミングパターンとモダンな機能をそなえ、プログラミングをより簡単に、柔軟に、そして楽しくします。Swiftは真っ新で、十分に発展し合いされて来たCocoaやCocoa Touchフレームワークによって裏打ちされています。これはソフトウェア開発がどのように回っていくのかを再考するいい機会になるでしょう。

という前段から始まるSwiftですが、その後の文章を読むに、ARCの導入であったり、Fast EnumerationやBlockなどの新しい機能をObjective-Cに導入していった結果導かれた、新たなプログラミング言語という位置づけのようで、それらのモダンな新機能の形跡が様々な箇所に見られます。

基本的には既存のObjective-Cにあった要素は保ちつつも、構文は大きく変更され、C言語のもつポインタや配列の添え字のような、セグメンテーションフォルトを起こしそうな系統の機能が省かれています。

そしてなによりも大きな要素として、関数型プログラミング言語からの文化を大きく受け、タプルやパターンマッチ、Maybe型、型推論など、プログラミングをより一層便利で柔軟にするための機構が盛り込まれています。

1.値

  • var myVariable = 42
  • myVariable = 50
  • let myConstant = 42

varは変数の宣言で、letは定数の宣言を表します。
型は書くこともできますが、省略することで型推論の機構が働きます。これらのmyVariableとmyConstantはどちらも整数型です。

  • let implicitInteger = 70
  • let implicitDouble = 70.0
  • let explicitDouble: Double = 70

上から順に、暗黙的な整数の変数、暗黙的なDoubleの変数、明示的なDoubleの定数となります。型を明示するときには、ECMAScript風な構文を用います。

  • let apples = 3
  • let oranges = 5
  • let appleSummary = "I have \(apples) apples."
  • let fruitSummary = "I have \(apples + oranges) pieces of fruit."

文字列中に数値を埋め込むには、\()で囲みます。囲まれたものはtoStringに相当する処理がなされます。

  • var shoppingList = ["catfish", "water", "tulips", "blue paint"]
  • shoppingList[1] = "bottle of water"
  • var occupations = [
  • "Malcolm": "Captain",
  • "Kaylee": "Mechanic",
  • ]
  • occupations["Jayne"] = "Public Relations"

通常の配列に加え、もちろん連想配列も利用可能です。宣言も非常に手軽になりました。

  • let emptyArray = String[]()
  • let emptyDictionary = Dictionary<String, Float>()

Javaでよく利用されるジェネリクス(Generics)が導入されました。このおかげでDictionaryが型安全になり、いままでNSDictionaryから取り出した値にコード補間が効かなかったイライラを解消してくれます。

配列やDictionaryにおけるMutableはなくなったようです。
※2014/06/03 23:39 訂正、letで宣言された配列、DictionaryはImmutable扱いで、varの場合はMutableとのことです。配列、Dictionaryに関する詳細は、
を参照してください。

2.制御構文

  • let individualScores = [75, 43, 103, 87, 12]
  • var teamScore = 0
  • for score in individualScores {
  • if score > 50 {
  • teamScore += 3
  • } else {
  • teamScore += 1
  • }
  • }
  • teamScore

for文、if分ともに括弧が省かれています。そしてFast Enumerationてきなfor ... in 構文が存在しており、ここにもObjective-Cのモダン機能からの進化の先にSwiftがいた形跡が見受けられます。

  • var optionalString: String? = "Hello"
  • optionalString == nil
  • var optionalName: String? = "John Appleseed"
  • var greeting = "Hello!"
  • if let name = optionalName {
  • greeting = "Hello, \(name)"
  • }

このあたりからだんだんとObjective-CやCから離れてきますが、注目すべきは1行目と4行目のString?と6行目のif let name = optionalName でしょう。String?はオプショナルのString型であることを表し、「正常系ではString型であるはずだが、エラーの際には値が存在しないかもしれない」ことを表す型で、Maybe型に近いものです。

そして、if let name = optionalName は「optionalNameに値が存在した場合は、それをnameと呼ぶことにする」という意味で、name変数はifの中括弧内で利用することができます。つまり、値があるかどうかをif文で判定し、あった場合にはString?型の変数optionalNameから?のとれたString型のname定数を作り出し、以後はname定数を利用して処理を書き続けることができるのです。

というわけで、いままで自然数を返す関数のエラー時の値として-1を返していたような箇所を、戻り値の型をint?にして、nilと返せばよくなりました。

また、nilのチェックがなされていないときにはコンパイルエラーが出るので、ぬるぽとはおさらばできそうです。

さらに、Swiftでは比較の処理が大幅にパワーアップしており、例えば

  • let vegetable = "red pepper"
  • switch vegetable {
  • case "celery":
  • let vegetableComment = "Add some raisins and make ants on a log."
  • case "cucumber", "watercress":
  • let vegetableComment = "That would make a good tea sandwich."
  • case let x where x.hasSuffix("pepper"):
  • let vegetableComment = "Is it a spicy \(x)?"
  • default:
  • let vegetableComment = "Everything tastes good in soup."
  • }

のような、switch文中に様々な条件を織り交ぜて書くことができるようになりました。
中でもlet x where x.hasSuffix("pepper")はなかなか興味深いですね。

また、連想配列のイテレーションには、下記のようなキーとバリューの変数名を指定して書くことができ、

  • let interestingNumbers = [
  • "Prime": [2, 3, 5, 7, 11, 13],
  • "Fibonacci": [1, 1, 2, 3, 5, 8],
  • "Square": [1, 4, 9, 16, 25],
  • ]
  • var largest = 0
  • for (kind, numbers) in interestingNumbers {
  • for number in numbers {
  • if number > largest {
  • largest = number
  • }
  • }
  • }
  • largest

関数型言語によく見られる、0..2のような、範囲を生成する演算子も新たに導入されました。
  • var firstForLoop = 0
  • for i in 0..3 {
  • firstForLoop += i
  • }
  • firstForLoop
  • var secondForLoop = 0
  • for var i = 0; i < 3; ++i {
  • secondForLoop += 1
  • }
  • secondForLoop

0..3は0からはじまり3まで、すなわち[0, 1, 2]を表します。そして構文的にかなり紛らわしいですが、中のドットが3つのバージョン0...3もあり、こちらは3を含み[0, 1, 2, 3]を表します。

3.関数とクロージャ

今までの説明で、Swiftがかなり関数型言語に影響を受けていることは明らかですが、ここで本題の関数が登場します。

  • func greet(name: String, day: String) -> String {
  • return "Hello \(name), today is \(day)."
  • }
  • greet("Bob", "Tuesday")

これは二つのString型の引数をとってString型の値を返す関数greetです。あの→が関数型感を醸し出していて非常にいいですね。

そして値を同時に複数返す関数も、タプルを使って便利に書くことができます。
※2014/06/04 0:42 訂正、タプル、ではなくタプルのようなもの、が正しい感じ
  • func getGasPrices() -> (Double, Double, Double) {
  • return (3.59, 3.69, 3.79)
  • }
  • getGasPrices()

関数はjavasctript等と同じく第一級オブジェクトとして扱われるので、高階関数も利用可能です。

  • func makeIncrementer() -> (Int -> Int) {
  • func addOne(number: Int) -> Int {
  • return 1 + number
  • }
  • return addOne
  • }
  • var increment = makeIncrementer()
  • increment(7)

配列の中の要素に一定の変換をかけて新たな配列を生み出すmap等も用意されており、引数として関数をとります。

  • numbers.map({
  • (number: Int) -> Int in
  • let result = 3 * number
  • return result
  • })

中括弧の中に丸括弧で引数を書き、in のあとから関数の本文が始まります。


4.オブジェクトとクラス

  • class NamedShape {
  • var numberOfSides: Int = 0
  • var name: String
  • init(name: String) {
  • self.name = name
  • }
  • func simpleDescription() -> String {
  • return "A shape with \(numberOfSides) sides."
  • }
  • }

まぁそれはオブジェクトとクラスくらいはありますよね。

ゲッターとセッターが若干おしゃれにかけるのは便利そう。

  • class EquilateralTriangle: NamedShape {
  • var sideLength: Double = 0.0
  • init(sideLength: Double, name: String) {
  • self.sideLength = sideLength
  • super.init(name: name)
  • numberOfSides = 3
  • }
  • var perimeter: Double {
  • get {
  • return 3.0 * sideLength
  • }
  • set {
  • sideLength = newValue / 3.0
  • }
  • }
  • override func simpleDescription() -> String {
  • return "An equilateral triagle with sides of length \(sideLength)."
  • }
  • }
  • var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
  • triangle.perimeter
  • triangle.perimeter = 9.9
  • triangle.sideLength

他にも、プロパティがセットされるときのフックすることもでき、

  • class TriangleAndSquare {
  • var triangle: EquilateralTriangle {
  • willSet {
  • square.sideLength = newValue.sideLength
  • }
  • }
  • var square: Square {
  • willSet {
  • triangle.sideLength = newValue.sideLength
  • }
  • }
  • init(size: Double, name: String) {
  • square = Square(sideLength: size, name: name)
  • triangle = EquilateralTriangle(sideLength: size, name: name)
  • }
  • }
  • var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
  • triangleAndSquare.square.sideLength
  • triangleAndSquare.triangle.sideLength
  • triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
  • triangleAndSquare.triangle.sideLength

指定されたプロパティの値がセットされるときに、別の値も書き換えるようなことができあます。この例では、triangleがセットされるときにはsquareのsideLengthを書き換え、squareがセットされるときにはtriangleのsideLengthを書き換えています。若干泥臭い感じもしますが、きっとこのサンプルがいけてないだけだと思います。

5.列挙型と構造体

Objective-Cには定数に毛が生えた程度のものしか無かった列挙型ですが、Swiftではクラスのような市民権を得たようです。

  • enum Rank: Int {
  • case Ace = 1
  • case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
  • case Jack, Queen, King
  • func simpleDescription() -> String {
  • switch self {
  • case .Ace:
  • return "ace"
  • case .Jack:
  • return "jack"
  • case .Queen:
  • return "queen"
  • case .King:
  • return "king"
  • default:
  • return String(self.toRaw())
  • }
  • }
  • }
  • let ace = Rank.Ace
  • let aceRawValue = ace.toRaw()

また、関数も定義できるようなので、列挙型の用途が飛躍的に広がるでしょう。
こちらもあわせてご覧ください。

構造体は以下のように書きます。

  • struct Card {
  • var rank: Rank
  • var suit: Suit
  • func simpleDescription() -> String {
  • return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
  • }
  • }
  • let threeOfSpades = Card(rank: .Three, suit: .Spades)
  • let threeOfSpadesDescription = threeOfSpades.simpleDescription()

クラスと何が違うんだ、という感じですが、構造体は常に値渡しで、クラスは参照渡しだそうです。なるほど!

6.プロトコルとエクステンション

  • protocol ExampleProtocol {
  • var simpleDescription: String { get }
  • mutating func adjust()
  • }

プロトコルは今までのプロトコルですね。ゲッターがいる、とかが明示的にかけるのがすばらしいのと、ポイントはmutatingでしょうか。これはデータを変更する可能性があるということを意味する修飾子で、この修飾子が無い場合にはデータを変更することはできないという意味です。この修飾子は構造体のみに利用するもので、クラスのメソッドがクラスの内容を書き換えるのはデフォルトでオッケーだそうです。副作用の有無をコンパイラがわかるようになるので、これは最適化に使われたり並列化されたりとか妄想が膨らみます。

7.ジェネリクス

  • func repeat<ItemType>(item: ItemType, times: Int) -> ItemType[] {
  • var result = ItemType[]()
  • for i in 0..times {
  • result += item
  • }
  • return result
  • }
  • repeat("knock", 4)

特定の型の引数を一つとって、それをtimes個並べて作った配列を返すrepeat関数なんかも、ジェネリクスを使うと綺麗に書くことができます。また、なにげに+=が配列の要素の追加の意味で使われているあたりもなかなか味わい深いコードです。

8.パターンマッチング

  • let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
  • // This code isn't valid.
  • for (x, 0) in points {
  • /* ... */
  • }

pointsの中に入っているもののうち、値が(x, 0)とマッチするもののみをfor文で回すというこのコード。つまりこの例でいうところの、(0, 0), (1, 0), (2, 0)のそれぞれに対して処理を行うことができる訳です。for文のブロックの内部ではxという変数をそのまま利用することができます。

※2014/06/03 15:15 訂正、このコードは動きません、ってしっかり書いてありました。。失礼しました。switch caseの際には動くようです。

  • let point = (1, 2)
  • switch point {
  • case (0, 0):
  • println("(0, 0) is at the origin.")
  • case (-2...2, -2...2):
  • println("(\(point.0), \(point.1)) is near the origin.")
  • default:
  • println("The point is at (\(point.0), \(point.1)).")
  • }

実際のものはこのような感じで、case (-2...2, -2...2):のところにマッチします。

こちらも参照

9.演算子のオーバーロード

  • struct Vector2D {
  • var x = 0.0, y = 0.0
  • }
  • @infix func + (left: Vector2D, right: Vector2D) -> Vector2D {
  • return Vector2D(x: left.x + right.x, y: left.y + right.y)
  • }

C++であるような演算子のオーバーロードにも対応しています。インクリメントなんかもオーバーロード可能。さらには特定の文字を使って独自の演算子なんかを定義することもできます。

  • @prefix @assignment func +++ (inout vector: Vector2D) -> Vector2D {
  • vector += vector
  • return vector
  • }

これの例では++のインクリメントならぬ+++の演算子を作っていて、前置+++演算子は対象の変数にもう一つ同じものを足し合わせた値を返すようにしています。すなわち二倍。

10.サブスクリプト

  • struct TimesTable {
  • let multiplier: Int
  • subscript(index: Int) -> Int {
  • return multiplier * index
  • }
  • }
  • let threeTimesTable = TimesTable(multiplier: 3)
  • println("six times three is \(threeTimesTable[6])")
  • // prints "six times three is 18"

配列にアクセスする際にはどのプログラミング言語においても大体variable[number]のような書き方を用いますが、Swift言語では配列以外に対しても同様な書き方で配列のごとく扱える、いわば配列の要素アクセスのオーバーロードを行うことができます。

例えば上の例では、TimeTable構造体の入った変数に対して、配列であるかのようにアクセスを行うと、添字の三倍の値が帰ってきています。添字はsubscript(index: Int) -> Intの引数として渡され、その関数の戻り値が実際の値として利用されます。

これを用いることで、DomのNodeListのような配列だけどアクセサを別に書いていたようなクラスに対して、より利便性の高い方法を提供することができるようになります。



総括

スクリプト言語だとか独自仕様だとかいろいろと騒がれていますが、この言語自体は非常によく設計されており、またXcodeとセットで出て来ているあたりが何ともニクい感じであります。

型をここまできっちりとコンパイラに理解させている上に、副作用の有無まで考慮させ、さらにLive Codingに対応しているとなると、このSwift言語はAppleが作ったおもしろ独自仕様の域を超えて、新たなプログラミング革命の火種となりうるレベルのものであるように思います。

他の関数型言語と比較してしまうと多少の見劣りをするかもしれませんが、GUI環境でこんなにたくさんのデバイスの上で動かすことのできる関数型言語は他に類を見ず、また現状のiOS/MaxOS開発者の数割がこの言語を用いることになれば、すぐにでも地球上で今最も書かれている関数型言語の地位を得ることになるでしょう。

ここから先は妄想ですが、Xcodeの補完機能がSwift言語に最適化されれば、大半の実行時エラーをコンパイルエラーとして検出することができるようになり、さらには大半の問題が型の問題として処理されるようになっていくのではないかあと考えています。そしてゆくゆくは、このSwift言語にCoq等の定理証明支援系からの技術を導入することで、テストコードを書く代わりに性質に関する証明を付与することもできるようになるかもしれません。そのときには、よりいっそう安全で質の高いコードが手軽に、いっそう効率的にかけるようになっていることでしょう。


このSwift言語はそんな未来を思わせてくれる、先進的な言語であると思います。これから先が非常に楽しみになって参りました。


Swift関連記事

0 件のコメント:

コメントを投稿