swift-lang 処理系のインストールからFizzBuzzプログラムの実行まで

今話題の Swift 言語を触ってみました。Linuxで。

処理系のインストール

バイナリからのインストールは、Quickstart guideというテキストに非常に簡潔にまとまっています。

$ wget  http://swift-lang.org/packages/swift-0.94.1.tar.gz
$ tar xvf swift-0.94.1.tar.gz
$ cd swift-0.94.1
$ export PATH=$PATH:$PWD/bin

バイナリからのインストール手順は、たったこれだけ。簡単!!! ソースコードからのビルドは、噂によると少し大変なようです。

Swift プログラムの実行には、Java の処理系が必要なようです。 手元の環境では元々インストールされていたため何もしてませんが、 必要に応じて適当に無料ダウンロードしてきたりしてください。

Hello, world!

まずはお約束、hello world の実行。 Quickstart guide に手順が書いてありますが、実行ファイルが置いてある場所が違うためそのままだとエラーになるので注意。

$ cd swift-0.94.1/examples/swift/tutorial
$ swift hello.swift
Swift 0.94.1 swift-r7114 cog-r3803

RunID: 20140610-2237-vkvmx0j2
Progress:  time: 火, 10 6 2014 22:37:45 +0900
Final status: 火, 10 6 2014 22:37:45 +0900  Finished successfully:1

実行すると、 hello とも world とも言ってくれず、よく分からない出力が出てきますが、ご安心を。 "Hello, world!" という文字列は、hello.txt にちゃんと出力されています。

$ cat hello.txt
Hello, world!

hello world を標準出力ではなくファイルにはき出す言語、TeXくらいだと思っていました。。。

FIzzBuzz を書いてみる

swift の tutorial を見ると、処理系ダウンロードの後にいきなりシミュレーションとか並列実行的なことをしていて、いささかハードルが高いように感じます。 swift 初の自作プログラムの題材としては FizzBuzz を書いてみようと思います。 作り方としては、hello world をベースに、user guide を読んで必要そうな構文要素をピックアップします。 大体以下のような要素が必要になると思います。

  • プログラムの全体構成
  • 標準出力へ文字を出す
  • ループ構文
  • 条件分岐

プログラムの全体構成

さて、まずは hello world のソースをじっと見つめます。

type messagefile;

app (messagefile t) greeting() {
    echo "Hello, world!" stdout=@filename(t);
}

messagefile outfile <"hello.txt">;

outfile = greeting();

swift はスクリプト言語ということで、先頭から順番に実行されていくのでしょう。 最後の方の outfile = greeting(); などを見ると、トップレベルに文が書けるようです。 上から順番に読んでいくと、いきなり型宣言をしているようなのが気になります。 type とはなんぞや。user guide を読んでみます。

In Swift, structures are defined using the type keyword (there is no struct keyword).

type キーワードで構造体が定義されるそうです。では、先頭の messagefile というのは構造体名なのでしょうか。 たしかに、app 関数(?) では messagefile t というような宣言をしているように見えます。これは messagefile 型の t という変数があるということなのでしょうか。

なんだか、hello world をこれ以上読んでも理解が深まりそうにない気がしてきたので、とりあえずプログラムを書いてみましょう。

app () fizz() {
  echo "asdf";
}
fizz();

さて、なんとなくこれで端末に asdf と出力されてくれそうな気がします。 実行してみましょう。

$ swift fizzbuzz.swift
Could not compile SwiftScript source: line 1:6: expecting an identifier, found ')'

コンパイルできないと叱られてしまいました。identifierが続いて欲しかったのに、')' が来てしまったと。 空の括弧を app の後に書くのは恐らくだめなのでしょうね。

括弧を消してみました。これならどうでしょう。

$ cat fizzbuzz.swift
app fizz() {
  echo "asdf";
}
fizz();
$ swift fizzbuzz.swift
Swift 0.94.1 swift-r7114 cog-r3803

RunID: 20140610-2254-0j82vqr6
Progress:  time: 火, 10 6 2014 22:54:59 +0900
Final status: 火, 10 6 2014 22:54:59 +0900  Finished successfully:1

実行できました!!! しかし、出力文字列は、、、どこ? hello.swift では echo の後に stdout=@filename(t) なんて指定しているので、この指定を省略すれば標準出力にはき出してくれるんじゃねーの?と思ったんですが、見込み違いだったようです。

標準出力へ文字を出す

毎回ファイルにはき出された文字を見るのはめんどくさいので、標準出力に文字を出したいのですが、なんとかならないものか。users guide を見てみましょう。

。。。簡単に調べてみましたが、見つかりませんでした。。。仕方ないので、強引に解決します。

type messagefile;

app (messagefile t) fizz() {
  echo "asdf" stdout=@filename(t);
}

messagefile outfile <"/proc/self/fd/0">;

outfile = fizz();

実行してみます。

$ swift fizzbuzz.swift
Swift 0.94.1 swift-r7114 cog-r3803

RunID: 20140610-2300-5trv3m7d
Progress:  time: 火, 10 6 2014 23:00:41 +0900
asdf
Final status: 火, 10 6 2014 23:00:41 +0900  Finished successfully:1

なんかもういきなり hack な感じがしてとてもアレですが、標準出力に何か出すという目的は達成できたので良いとします。

ループ構文

さて、FizzBuzz を作る上で、必要なのがループ構文。 100回 echo を書いても良いのですが、さすがにそれは無駄だしめんどくさいです。 手続き型言語ならループ構文の一つや二つぐらいあるだろうし、気軽にできるでしょう。多分。 users guide を見てみましょう。Control Constructs なんて節がありますね。まさにコレだ。

ループには、 foreachiterate というものがあるようです。 foreach は配列 (または、コレクション全般?) の列挙に使うものでしょうし、コード例もそれっぽいです。 ここでは iterate を使うのが適切でしょう。

iterate のコード例を引用します。

iterate i {
    trace(i); // will print 0, 1, and 2
} until (i == 3);

do-while みたいですね。上記は、 iuntil の条件を満たすまでブロック内を実行するというコードです。 最後のi == 0 だと無限ループするのか、それとも一度もループが回らないのか、とても気になりますね。やってみましょう。

iterate i {
  trace(i);
} until (i == 3);

そもそも、コードを app のブロック (関数?) で囲む必要あったんだっけ?と疑問になったので、トップレベルでいきなりループを回すようにしてみました。 実行結果。

$ swift iterate.swift
Swift 0.94.1 swift-r7114 cog-r3803

RunID: 20140610-2320-u9xc7f29
Progress:  time: 火, 10 6 2014 23:20:21 +0900
SwiftScript trace: 0
SwiftScript trace: 1
SwiftScript trace: 2
Final status: 火, 10 6 2014 23:20:21 +0900

うまく動いているようです。 trace という関数名を見たときに、これは端末に出力するときに変な工夫しなくても簡単に出す方法あったかも、、、と思ったのですが、表示は上記のように完全にデバッグ向けっぽいので、余計なものなしに出すにはやっぱりさっきの hack が必要だったみたいですね。 さて、肝心の、until の中身を i == 0 にした場合についてやってみましょう。

iterate i {
  trace(i);
} until (i == 0);
$ swift iterate.swift
Swift 0.94.1 swift-r7114 cog-r3803

RunID: 20140610-2323-ag8rbbj0
Progress:  time: 火, 10 6 2014 23:23:51 +0900
SwiftScript trace: 0
SwiftScript trace: 1
SwiftScript trace: 2
SwiftScript trace: 3
SwiftScript trace: 4
...

無限ループです!!!これはますます do-while 感が高まりますね。 foreach は分かるのですが、もう一つのループ構文として while でも for でもなく do-until だけ採用しているというのもなかなか変わっている気がします。 users guide に書いていないだけかも知れませんが。。。

さて、これで FizzBuzz に必要な1から100まで数をループするというのはできそうですね。書いてみましょう。

type messagefile;

app (messagefile t) fizz() {
  iterate i {
    echo i stdout=@filename(t);
  } until (i >= 99);
}

messagefile outfile <"/proc/self/fd/0">;

outfile = fizz();
$ swift fizzbuzz.swift
Could not compile SwiftScript source: line 4:3: expecting an identifier, found 'iterate'

まさかのコンパイルエラー!!!!これは困る。users guide をざっと見た感じ、appはなんか特殊なプロシージャのようです。 ループとか書けないのかも。ちょっとよく分からないので、完全な理解は後で users guide を読んだ後にするとして、ここではなんか回避策を考えます。 これでどうでしょうか。

$ cat fizzbuzz.swift
type messagefile;

app (messagefile t) fizz() {
  echo "aaaa" stdout=@filename(t);
}

messagefile outfile <"/proc/self/fd/0">;

iterate i {
  outfile = fizz();
} until (i >= 99);
$ swift fizzbuzz.swift
Could not start execution
        Compile error in procedure invocation at line 10
        variable outfile is not writeable in this scope

コンパイルエラーは出なくなりましたが、実行時エラーが出ました。 このスコープでは outfile 変数は writeable ではない、と。 Rust の lifetime とか &mut の aliasing に関するエラーメッセージみたいですね。。。

スコープが駄目と言われたので、こうしてみました。

type messagefile;

app (messagefile t) fizz() {
  echo "aaaa" stdout=@filename(t);
}

iterate i {
  messagefile outfile <"/proc/self/fd/0">;
  outfile = fizz();
} until (i >= 99);

同じスコープで宣言した変数なら問題あるまい。

$ swift fizzbuzz.swift
Swift 0.94.1 swift-r7114 cog-r3803

RunID: 20140610-2335-r16kd5ig
Progress:  time: 火, 10 6 2014 23:35:57 +0900
aaaa
Duplicate mapping found:
        outfile (line 8) and outfile (line 8) are both used to write to file://localhost//proc/self/fd/0
aaaa
Execution failed:
        Exception in echo:
    Arguments: [aaaa]
    Host: localhost
    Directory: fizzbuzz-20140610-2335-r16kd5ig/jobs/m/echo-mtf8gvrl
Caused by:
        The cache already contains localhost:fizzbuzz-20140610-2335-r16kd5ig/shared/proc/self/fd/0.
        fizz, fizzbuzz.swift, line 9

うひー、なんかだめみたいです。同じ output を複数回使う事はできないのでしょうか。うーん。。。よくわからん。。。

ちょっとここであまり悩むのもあれなので、標準出力について妥協します!!!!

iterate i {
  trace(i);
} until (i >= 99);

ソースがかなり短くなりました。

$ swift fizzbuzz.swift
Swift 0.94.1 swift-r7114 cog-r3803

RunID: 20140610-2338-zck3zvnd
Progress:  time: 火, 10 6 2014 23:38:03 +0900
SwiftScript trace: 0
SwiftScript trace: 1
SwiftScript trace: 2
SwiftScript trace: 3
SwiftScript trace: 4
SwiftScript trace: 5
SwiftScript trace: 6
...
SwiftScript trace: 96
SwiftScript trace: 97
SwiftScript trace: 98
Final status: 火, 10 6 2014 23:38:03 +0900

できた!!!けど、数字が98までしか出ていません。

iterate i {
    trace(i);
    int j = i; // will print 0, 1, 2, and 3
} until (j == 3);

上記のサンプルプログラムで 0, 1, 2, 3 が出るなら、99まではいける、と思ったのですが、だめでした。 よくよく見ると、 int j = i; がとても重要なようです。users guide より引用します。

Variables declared inside the body of iterate can be used in the termination expression. However, their values will reflect the values calculated as part of the last invocation of the body, and may not reflect the incremented value of the iteration variable:

until 実行時点で i にはインクリメントされた値が入っているけど、j は body を抜ける時の i の値が入っている、ということみたいです。 つまり実行順序が、

  1. ji を代入
  2. i をインクリメント
  3. until の条件を判定

となるようです。うーん。な、なるほど。。。

というわけでプログラムを直してみました。 よくよく考えると FizzBuzz は 1 から 100 までの数字を出さなければいけないので、それも直しました。

iterate i {
  trace(i + 1);
} until (i == 100);

これで、1から100までが出力されました。

条件分岐

さて、ここまで来たらあと一息ですね! if の構文は C と同じようです。また、整数の剰余の演算子%% のようです。 ここまで分かれば、書けますね!!!

iterate i {
  int n = i + 1;

  if (n %% 3 == 0 && n %% 5 == 0) {
    trace("FizzBuzz");
  } else if (n %% 3 == 0) {
    trace("Fizz");
  } else if (n %% 5 == 0) {
    trace("Buzz");
  } else {
    trace(n);
  }

} until (i == 100);
$ swift fizzbuzz.swift
Could not compile SwiftScript source: line 6:10: expecting '{', found 'if'

前言撤回。C の if とは違い else{} は省略できないようです。

iterate i {
  int n = i + 1;

  if (n %% 3 == 0 && n %% 5 == 0) {
    trace("FizzBuzz");
  } else {
    if (n %% 3 == 0) {
      trace("Fizz");
    } else {
      if (n %% 5 == 0) {
        trace("Buzz");
      } else {
        trace(n);
      }
    }
  }

} until (i == 100);

入れ子が深いですが、できました。実行します。

$ swift fizzbuzz.swift
Swift 0.94.1 swift-r7114 cog-r3803

RunID: 20140610-2349-qkryujr6
Progress:  time: 火, 10 6 2014 23:49:45 +0900
SwiftScript trace: 1
SwiftScript trace: 2
SwiftScript trace: Fizz
SwiftScript trace: 4
SwiftScript trace: Buzz
SwiftScript trace: Fizz
SwiftScript trace: 7
SwiftScript trace: 8
SwiftScript trace: Fizz
SwiftScript trace: Buzz
SwiftScript trace: 11
SwiftScript trace: Fizz
SwiftScript trace: 13
SwiftScript trace: 14
SwiftScript trace: FizzBuzz
SwiftScript trace: 16
SwiftScript trace: 17
SwiftScript trace: Fizz
SwiftScript trace: 19
SwiftScript trace: Buzz
SwiftScript trace: Fizz
SwiftScript trace: 22
SwiftScript trace: 23
SwiftScript trace: Fizz
SwiftScript trace: Buzz
SwiftScript trace: 26
SwiftScript trace: Fizz
SwiftScript trace: 28
SwiftScript trace: 29
SwiftScript trace: FizzBuzz
SwiftScript trace: 31
SwiftScript trace: 32
SwiftScript trace: Fizz
SwiftScript trace: 34
SwiftScript trace: Buzz
SwiftScript trace: Fizz
SwiftScript trace: 37
SwiftScript trace: 38
SwiftScript trace: Fizz
SwiftScript trace: Buzz
SwiftScript trace: 41
SwiftScript trace: Fizz
SwiftScript trace: 43
SwiftScript trace: 44
SwiftScript trace: FizzBuzz
SwiftScript trace: 46
SwiftScript trace: 47
SwiftScript trace: Fizz
SwiftScript trace: 49
SwiftScript trace: Buzz
SwiftScript trace: Fizz
SwiftScript trace: 52
SwiftScript trace: 53
SwiftScript trace: Fizz
SwiftScript trace: Buzz
SwiftScript trace: 56
SwiftScript trace: Fizz
SwiftScript trace: 58
SwiftScript trace: 59
SwiftScript trace: FizzBuzz
SwiftScript trace: 61
SwiftScript trace: 62
SwiftScript trace: Fizz
SwiftScript trace: 64
SwiftScript trace: Buzz
SwiftScript trace: Fizz
SwiftScript trace: 67
SwiftScript trace: 68
SwiftScript trace: Fizz
SwiftScript trace: Buzz
SwiftScript trace: 71
SwiftScript trace: Fizz
SwiftScript trace: 73
SwiftScript trace: 74
SwiftScript trace: FizzBuzz
SwiftScript trace: 76
SwiftScript trace: 77
SwiftScript trace: Fizz
SwiftScript trace: 79
SwiftScript trace: Buzz
SwiftScript trace: Fizz
SwiftScript trace: 82
SwiftScript trace: 83
SwiftScript trace: Fizz
SwiftScript trace: Buzz
SwiftScript trace: 86
SwiftScript trace: Fizz
SwiftScript trace: 88
SwiftScript trace: 89
SwiftScript trace: FizzBuzz
SwiftScript trace: 91
SwiftScript trace: 92
SwiftScript trace: Fizz
SwiftScript trace: 94
SwiftScript trace: Buzz
SwiftScript trace: Fizz
SwiftScript trace: 97
SwiftScript trace: 98
SwiftScript trace: Fizz
SwiftScript trace: Buzz
Final status: 火, 10 6 2014 23:49:45 +0900

結果だけ見ると、普通の FizzBuzz プログラムですが、妙に遠回りしてしまった気がします。。。

まとめ

Swift の面白そうなところにはたどり着けませんでしたが、エラーメッセージ等から何か裏に潜んでいそうな気配を感じ、なかなか楽しめそうです。 ひとまず今回は体当たりしてみましたが、次はもう少し体系的に学んでからちゃんとしたプログラムを書きたいと思います。 あと、面白そうな言語を紹介してくれた Apple に感謝です。