251
246

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 5 years have passed since last update.

Swiftは○○に似ている

Last updated at Posted at 2014-06-03

はじめに

AppleがSwiftという言語を発表した。
新しい言語をつくるのであれば既存の言語から良いアイディアは取り入れ悪いアイディアは取り入れないのが良い。

TwitterのTLを見ていると「Swiftは○○に似ている」という発言があったので、
どこら辺が似ているのかを自分の中で振り返ってみた。

(最初は「Swiftは○○のパクり」というタイトルにしたかったのだが、文全体を挑発的に書けなかったのでやめた)

間違っていることもあるはずなので、コメントは大歓迎である。

Swift

全部紹介するのは無理なので、とりあえず A Swift Tour から引用したい。

基本文法

基本文法はC言語系だ。現在の手続き型言語はC++やJava等これが主流である。
(主流でない文法はFORTRAN、BASIC、Pascalなどなど。関数型だとまた全く違う)

Simple Values

var myVariable = 42
myVariable = 50
let myConstant = 42

letvarのように、型からはじめるのではなく変数宣言から始める言語は結構多い。
知る中ではVisualBasicが最古。 VB以前のBASICでもDIMという変数宣言をしていたそうだ。(コメントより)
こちらの文法のほうがパーサーが書きやすいという利点がある。

変数宣言で再代入できるか出来ないかを分けるのは私の知る限りではScalaが最初だった。
それより前の言語、例えばJavaでは、final修飾子をつけるなどして再代入を防いでいた。

let appleSummary = "I have \(apples) apples."

文字列中に式を埋め込むのはかなり古い言語からある。私の知る中ではBashが最古。
バックスラッシュ\で式展開をするのは初めて見た。

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

配列と連想配列に専用の初期化構文を置くのはScript言語だとよくある。知る中ではPerlが最古。
多くの場合は、Array,Dictionaryの専用の構文になっていることが多い。(TODO: Swiftでは専用構文なのか後で調べる)
(C++では汎用的な初期化構文がある。C#では配列とは別だがコレクション初期化構文がある)

Control Flow

for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}

よくある。括弧()を使わないのは少し珍しいが、例えば Rust, Go などがある。

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

null(Swiftの場合は nil)を含むかもしれない値にoptional? マークを付けるのは珍しくない。知る中ではKotlinが最古だがアイディア自体はかなり昔から見た覚えがある、なんだっけか。
C#では値型でのみNullableが利用できる。(TODO: Swiftのnilたりえる変数全てに?がつくのかライブラリをのぞいてみる)

ifで 代入+optionalの判別 をしているのを見たのはSwiftが初めて。正直KotlinやHackのようなコンテキストで判断するタイプのほうが(初見ではとっつきにくいが)スマートで良い。
代入を含むifではletが一緒に出てくるので、よくある「比較と代入を間違える」ということが無いのは素晴らしい。

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-caseに複雑なことを書ける言語は多い。スクリプト言語だとほとんどがそうで、関数型言語でもパターンマッチがある。C++, Java, C#のように「ジャンプ命令に変換」するタイプだとできない。
C系から比較するとbreakが不要なところに好感がもてる。

for (kind, numbers) in interestingNumbers {
  /* ... */
}

for文でtupleにマッチさせるのは珍しい気がする。知る中ではScalaが最古。
正確に言えば「for文」と「パターンマッチ」の両方を持つ言語が少ないのではないか。

for文のループ変数に二値が使えるのは、Rubyなどいろいろある。
他にも「静的型付き関数型言語では変数を導入できる場所にパターンが書ける」とのこと。

(TODO: Tuple以外でもマッチできるのか調べる。要はExtractor?の存在)
extractorじゃなくて =~というのがあるそうだ。Rubyの===っぽい。

for i in 0..3 {
    firstForLoop += i
}

..による範囲式はPerlが最初かな?(TODO: 式なのかfor構文の一部なのか調べる)

Use .. to make a range that omits its upper value, and use ... to make a range that includes both values.

とあるけどRubyと逆じゃないだろうか。そういうのはやめてほしい。

Functions and Closures

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

型定義を後ろに書くパターン。関数型言語だと一般的。コロン:なのは、知る中でのOCamlが古いので最古はMLだろうか。Goでも型は後ろに書くが、コロンは書かないようだ。(後述)

func getGasPrices() -> (Double, Double, Double) {
    return (3.59, 3.69, 3.79)
}

Tupleの戻り値の型を宣言して、Tupleの値を返してる。関数型言語だと一般的。

func sumOf(numbers: Int...) -> Int {
    /* ... */
}
sumOf()
sumOf(42, 597, 12)

可変長引数を提供する言語は多い。
しかしC言語はマクロを用いて面倒なことをしていた。型付きの言語で言語構文に組み込んだのはJavaが最古だろうか?
可変長引数と部分適用は相性が悪いので関数型言語では見ないイメージ(わざわざ可変長にしなくてもリストでいいじゃんというね)

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

高階関数を扱えるのは関数型言語だと当然だが、手続き型言語でも主流になってきた。
C言語でも関数ポインタは渡せるが、シグネチャが複雑になるのでよろしくない。
C++は関数内関数は書けてもクロージャを作らない。もしくは匿名関数の構文。

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

ラムダ式にinを使うのは初めて見た。
OCamlに let-in という構文があって let x = 3 in x * x という感じに使う。
使われる場所は違うけど、inの前で宣言、後ろでそれを利用した定義のところに同じ雰囲気を感じる。
型推論されるので、ラムダ式の型は省略できるようだ。

sort([1, 5, 3, 12, 2]) { $0 > $1 }

暗黙の引数名 $0はスクリプト言語だとよくある。記憶が定かではないが型付きの言語でもあったはず。
Scalaのプレースホルダーと違って{,}で囲まれていてラムダ式の範囲が明確。引数の順序を指定できるあたりがうれしい。

Objects and Classes

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

普通。Java, C#っぽい。メンバ変数でも型推論が効くのはうれしい。

var shape = Shape()

コンストラクタにnewが要らない。知る限りではPythonが最古。Scalaだとコンパニオンオブジェクトを作ってnewをなくしてしまうことがある。

追記:

C++でもnew無しにコンストラクタを呼べる。というよりnewの有無によって振る舞いが異なり、newすると新しくヒープから確保し、そうでないと「その場所(関数内だとスタック)」に確保する。

init(sideLength: Double, name: String) {
    self.sideLength = sideLength
    super.init(name: name)
    numberOfSides = 4
}

詳しく見ないといけないが、親クラスのコンストラクタを呼ぶ前に処理が入っている。C++, Java, C#でこういうのは出来ない。後述。
代入だけだからいけるのだろうか?(TODO: 調べる)

class EquilateralTriangle: NamedShape {
    /* ... */
    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)."
    }
}

プロパティはあって当然な機能な気がするけど、知る中ではなぜかC#が最古。 Delphiにプロパティ構文があるそうだ。
Javaのgetter/setterはカッコ悪いよね。
override必須なのも(Javaはミスしたが)最近は一般的。

class TriangleAndSquare {
    var triangle: EquilateralTriangle {
    willSet {
        square.sideLength = newValue.sideLength
    }
    /* ... */
}

willSetというのは初めて見た。プロパティ代入へのフックのようだが専用構文は必要なのだろうか。

Enumerations and Structures

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"
    /* ... */
}

知る中ではC#っぽいけどintから型付きのenumを作るのは他にもありそう。(D言語とかどうだっけ)
enumのswitch文が.Aceのようにドットで始まるのは初めて見た。なんでだろう。

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}

値型と参照型をstructclassで分けるところがC#っぽい。(D言語も同様)

enum ServerResponse {
    case Result(String, String)
    case Error(String)
}

variantとか判別共用体とか呼ばれる機能ですね。関数型だとよくある。
「optional?」とこの機能が両方存在しているところは突っ込みたい。
(TODO: 実は一緒だったりしないか調べる)

Optional<T>の糖衣構文のようだ。Array<T>も同様のよう。

Protocols and Extensions

インタフェースです。
インスタンスメソッドだけではなく、クラスメソッドも制約をつけられるようだ。

struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}

mutatingというのは初めて見た。値型が自身を書き換える関数にマークする。
おそらくstructをprotocolにキャストするときにboxingされてその結果コピーだけが書き換わって元の値が書き換わらないというC#でよくあるバグを防ぐのに使うのかなと思われる。 用途はこれだけではないようだ。

コメントを参照してください。

Generics

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

C#の総称型と同様だと思われる。structがあるので型消去はされないと信じたい(TODO: 後で調べる)

func anyCommonElements <T, U where T: Sequence, U: Sequence, T.GeneratorType.Element: Equatable, T.GeneratorType.Element == U.GeneratorType.Element> (lhs: T, rhs: U) -> Bool {
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}
anyCommonElements([1, 2, 3], [3])

総称型の制約の書き方はJavaとC#の間をとったようだ。(Javaのように<>の中に制約を書くが、C#のように関数名の後ろに宣言する。後述)

C#やJavaとは違い高階型(型引数を取る型、この名称であってる?)の制約もできるようだ。
構文は全然違うが、HaskellやScalaに存在する。

さいごに

自分の知ってる言語と比較して振り返ってみたけど、良いところを取り込んだ普通によさそうな言語だった。
今後のAppleに期待したい。

Appendix

Goの関数宣言

Goはコロン無しで型を後ろに書く。
これでx,yの両方がintだと指定できるのがちょっと珍しい。

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

Javaのコンストラクタの例

例えばこんなコードがコンパイルできない。

class Parent {
  private int pValue;
  Parent(int value) {
    pValue = value;
  }
}
class Child {
  private int cValue;
  Child(int value) {
    cValue = value; // 親のコンストラクタより先にフィールドを初期化
    super(0);
  }
}

JavaとC#の総称型

Java

public <T extends CharSequence, U extends List<Object>> int foo (T str, U list) {
	return 0;
}

C#

public int Foo<T, U>(T str, U list)
		where T : IEnumerable<char>
		where U : List<Object> {
	return 0;
}
251
246
13

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
251
246

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?