Crystal
本書は、Crystalのチュートリアルです。 Crystalは、Ary Borenszweig、Juan Wajnerman、Brian Cardiffと300人以上の貢献者によって設計・開発された汎用オブジェクト指向プログラミング言語です。Ruby にヒントを得た構文を持ち、静的型付けなコンパイル型言語ですが、変数やメソッドの引数の型は一般には不要です。型は高度なグローバル型推論アルゴリズムによって解決されます。CrystalはApache Licenseバージョン2.0のもと、FOSSとしてリリースされています。
特徴

Crystalは、Rubyのシンタックスを持つ静的型付けのプログラミング言語で、以下のような特徴があります。
Hello, World!

お約束のHello_worldですが、ここではRubyとの比較も兼ねて、Ruby#Hello, World!をそのまま実行してみます。
Hello, World!

hello.crを用意します。
特徴

実際にCrystalで書かれたのコードを見てみましょう。
特徴

Crystalを使ってファイルを読み込むには、Fileクラスを使用します。
特徴

以下は、Crystalでファイルを読み込む例です。
特徴

File.openにブロックを渡すと、ブロックを抜けると自動的にファイルが閉じられます。明示的なファイルのクローズが不要です。
特徴

このコードは、ファイルを開いて内容を読み込む際にブロックを使用し、ブロックを抜けるとファイルが自動的に閉じられるというCrystalの一般的なイディオムを示しています。 この方法は、ファイルの操作後にクローズ処理を忘れることを防ぎ、コードをより安全かつ簡潔にします。
特徴

エラトステネスの篩を、若干 Crystal らしく書いてみました。
プログラミング環境

Crystalのプログラムを作り、コンパイル・実装するには、「オンライン実行環境を使う」・「エディト・コンパイル・実行環境を用意してそれを使う」の2通りの方法があります。
プログラミング環境

公式のオンライン実行環境、 https://play.crystal-lang.org/ があります。 まずは、これを使って本書に例示されているコードを実行してみることをお勧めします。
プログラミング環境

エディタについては本書では触れませんが、プログラミング時間の大半はエディタの操作に費やされるため、良いエディタを選択することが重要です。
プログラミング環境

Crystal の言語処理系は、 https://crystal-lang.org/install/ から入手します。
プログラミング環境

自分の、OSやGNU/Linuxであればディストリビューションに合わせてインストールしてください。 また、FreeBSDのように crystal と shards が別パッケージとなっていることもあるので、その場合は shards も追加インストールします。
プログラミング環境

多くの場合、インストールされた crystal はスタティック リンクされているので、ダイナミック リンク版の crystal を入手するには、ソースコードからビルドします。 また、interactive Crystalを有効にするためにも、ソースコードからのビルドが必要です。
プログラミング環境

crystal は、ソースコードが Github の https://github.com/crystal-lang/crystal.git で公開されているので、必要に応じてソースコードからビルドすることができます。 crystalは、セルフホスティング言語なので、最初にバイナリーを入手してブートストラップするか、クロスビルドしたバイナリーを持込むか、パッケージシステムからインストールし、ターゲットでセルフコンパイル出来る状態を作る方法があります。
プログラミング環境

ビルドには、Chromebook(メモリー4GB, Celeron N4000, OS Version: reven-release/R104-14909.124.0, Chromebrew version: `1.25.3`, llvm-15.0.0)で約30分かかりました。
プログラミング環境

crystal コマンドは Crystal のコンパイラであると同時に、ビルドツールなどを含んだツールチェインです(プログラミング言語のCrystalは、先頭を大文字、コマンドのcrystalは、先頭を小文字にして区別します)。
プログラミング環境

[TODO: コマンドラインツール crystal の解説。 crystal ファイル名 は crystal run ファイル名 の短縮形で、インタープリタ的な実行...ではなく、内部ビルドツールでコンパイル・実行を行う]
Ruby との違い

Crystalは、Rubyに触発された構文を持つものの、Rubyとの互換性をゴールに定めてはいません。 このため、細部を見ると仕様に差異があり、Rubyのソースコードをcrystalに掛けても前節の 'Hello World' の様にコンパイルに失敗することがあります。 また、コンパイルできても実行結果に違いが出ることがあります。
Ruby との違い

ここでは、Ruby との違いについて実際のコードと双方の結果を比較することで、差異についての理解を深めていきます。
Ruby との違い

big を require すると BigInt が使えるようになります。
Ruby との違い

Crystalでは、Set(集合)は組込みクラスなので、require "set"は不要です。
Ruby との違い

Crystal には、Ruby にはある for式がありません。
Ruby との違い

また loop 式もありませんが while true; ... end で間に合います。Ruby では while 式の条件の次に do が置けますが、Crystal では置けません。
Ruby との違い

Rubyのforに似せるという縛りがなければ、(マクロを使うまでもなく)簡単に実装できます。 偶然ですが、Scalaのforメソッドに似てしまいました(あれも、イテレーション メソッドに展開されるのである程度は必然)。Scalaと同じ様にジェネレターと組合わせて多次元に拡張することもできそうです。
Ruby との違い

Crystal には eval() はありません。 Crystalはコンパイル型言語ですので、無理もないことです。 もし、Crystal で eval() を実装しようとすると、Common Lisp の様にインタープリターを丸ごとランタイムに含む必要があります。 これはリーズナブルな選択ではありません。 Crystal では、eval() が必要なケースに(限定的ですが)マクロを使うことで実現出来る可能性があります。
Ruby との違い

Rubyでは、Arrayなどのeachメソッドは self を返し、break で中断すると nil を返します。
Ruby との違い

そこで、このようなコードがかけます。
Ruby との違い

これに対し Crystal では、Arrayなどのeachメソッドは self を返さない(常に nil を返す)ので上記のようなトリッキーなコードはかけずフラッグを使います。
Ruby との違い

Crystalには、Rubyにはないマクロがあります。Rubyは実行時にすべてのオブジェクトにアクセス出来て、メソッド生やし放題なのでマクロは必要ありませんが、Crystalはコンパイル時に型やメソッドを確定する必要があり、特にメソッドジェネレターとしてのマクロにニーズがあります。また、テンプレート言語的なマクロなので、環境変数による条件分岐や、コンテナを渡し繰返し処理する構文もあります。
Ruby との違い

Ruby#ユーザー定義クラスの都市間の大圏距離を求めるメソッドを追加した例を、Crystalに移植しました。
Ruby との違い

JavaScript/クラス#包含と継承を、Rubyに移植したRuby#包含と継承を、Crystalに移植しました。
Ruby との違い

Crystal には、RubyのClassにあるメソッド superclass と subclasses がないので、マクロで実装しました。
Ruby との違い

Java/抽象クラスを、Crystalに移植しました。
Ruby との違い

RubyとCrystalの主な文法の違いを表形式でまとめました。
キーワード

Crystalのキーワード( keywords ) は、以下の通り。
キーワード

abstract alias as asm begin break case class def do else elsif end ensure enum extend for fun if include instance_sizeof lib macro module next of out pointerof private protected rescue return require select sizeof struct super then type typeof uninitialized union unless until when while with yield
演算子

Crystalは、1つ、2つ、または3つのオペランドを持つ数多くの演算子をサポートしています。
演算子

演算子式は、実際にはメソッド呼び出しとしてパースされます。例えば、a + b は a.+(b) と意味的に同じで、引数 b を持つ a のメソッド + を呼び出すことになります。
制御構造

制御構造(せいぎょこうぞう、control flow)とは、「順次」「分岐」「反復」という基本的な処理のことを言います。
制御構造

Crystalの条件分岐には、if, until と caseの3つの構文があります。
制御構造

ifは条件式によって実行・否を切り替える構造構文で、評価した式の値を返すので条件演算子でもあります。
制御構造

Crystalには、RubyやPerlのような後置のifがあります。
制御構造

unless(アンレス)は条件式によって実行・否を切り替える構造構文ですが、ifとは条件式に対する挙動が逆です。
制御構造

Crystalには、RubyやPerlのような後置のunlessがあります。
制御構造

caseは、複数の条件式によって処理を降る分ける用途の為に用意されています。
制御構造

ifを使ったコードをcaseに書き換えてみましょう。
制御構造

このコードは when 節の式の値とcaseの式を === で比較し、最初に一致した when に対応する式が実行される事を利用しています。
制御構造

when 節が式ではなく型であった場合、caseの式を is_a? で評価し、最初に一致した when に対応する式が実行されます。
制御構造

暗黙のオブジェクト構文を使うと
制御構造

when に対応する式は、1つのことが珍しくないので、その場合は省略可能な then を補うと、1行で書けます。
制御構造

Crystalにおいて、タプルとは、複数の値をグループ化して1つのオブジェクトとして扱うための機能です。タプルは、角括弧 [ ] 内にコンマで区切られた値を指定して作成します。
制御構造

例えば、次のように2つの値を持つタプルを作成できます。
制御構造

タプルの要素にアクセスするには、インデックスを指定して取得します。
制御構造

また、Crystalにはダミー識別子 _ があります。この識別子は、未使用の引数や変数を表すために使用されます。例えば、次のように _ を使って、2番目の要素を無視して1番目の要素だけを取得することができます。
制御構造

ここでは、2番目の要素を _ で受け取っています。このように、ダミー識別子 _ を使うことで、ある変数や引数が使用されないことを明示することができます。
制御構造

when の代わりに in を使用すると、exhaustive case 式が作成されます。exhaustive case では、必要な in 条件を省略するとコンパイル時にエラーとなります。exhaustive case 式では、when 節と else 節を含むことはできません。
制御構造

[TODO:短絡評価 && || ]
制御構造

Crystalには、他のプログラミング言語のような繰返し構文と、イテレーターメソッドがあります。
制御構造

Crystalの繰返し構文には、while と untilの2つがあります。
制御構造

while(ホワイル)は条件が真である間、式を実行しつづけます。
制御構造

until(アンティル)は条件が偽である間、式を実行しつづけます。whileとは条件に対する挙動が逆です。
制御構造

Crystalにはforがありませんが、コレクションのイテレーションメソッドを使うことで繰返しを簡素に実現出来ます。
制御構造

Rangeオブジェクトは、整数の区間を表し範囲演算子 開始 .. 終了 や 開始 ... 終了 で生成します。 範囲演算子の終了は省略でき、その場合は数学の半開区間(半閉区間)となり、例えば、1 ..は自然数となります(ただし、日本的な0を自然数に含まない場合)。
制御構造

Arrayオブジェクトは、任意の Crystal オブジェクトを要素として持つことができます。 配列式[ 要素1, 要素2, ... 要素n ] で生成します。
制御構造

Tupleオブジェクトは、任意の Crystal オブジェクトを要素として持つことができます。 配列式{ 要素1, 要素2, ... 要素n } で生成します。
制御構造

Setオブジェクトは集合です。任意の Crystal オブジェクトを要素として持つことができますが、1つの値は重複して持てません。 Set.newに配列式{ 要素1, 要素2, ... 要素n } などを渡し初期化します。
制御構造

NamedTupleオブジェクトは、任意の Crystal オブジェクトをキーに、任意の Crystal オブジェクトを値に持つことができる連想配列です。 NamedTuple式{キー1 => 値1, キー2 => 値2, キーn => 値n} で生成します。 また、キーが Symbol の場合 NamedTuple式{キー1: 値1, キー2: 値2, キーn: 値n} で生成することが出来ます。
制御構造

このように、Crystalではforがなくてもコレクションのメソッドで同様の処理を実現できます。
制御構造

loop ありません。 while true で代用します。
制御構造

Integer#timesは与えられたブロックをオブジェクトの示す整数値回くりかえします。
オブジェクト

Crystal では、全てがオブジェクトです。
メソッド

オブジェクトの値や機能を呼び出すためには、メソッドを使います(多くの演算子もメソッドです)。
メソッド

Crystal には、Objectクラスにmethodsメソッドがないので、マクロで実装しました。
マクロ

マクロには {{attr.id}} の様にASTへのアクセス手順が用意されており、半ば言語を拡張するようなアプローチを取ることも出来ます。
マクロ

Crystalのマクロは、実行時ではなくコンパイル時に実行される機能であり、ソースコードを操作することができます。以下はCrystalのマクロの主な機能と構文の説明です。
マクロ

マクロを定義するときに、引数に * を付けることができます。これにより、任意の数の引数を受け取ることができます。例えば、以下のマクロは、任意の数の引数をコンマで区切った文字列を出力します。
マクロ

このマクロを使用すると、以下のようにコードを記述できます。
マクロ

マクロの中で、{{}}で囲んだ式を埋め込むことができます。この式は、コンパイル時に評価され、その結果がコードに挿入されます。例えば、以下のマクロは、引数に指定された式を2倍にして返します。
マクロ

このマクロを使用すると、以下のようにコードを記述できます。
マクロ

マクロの中で、{% ... %}で囲んだ部分は、コンパイル時に評価されますが、その結果はコードに挿入されません。代わりに、{% ... %}で囲まれたコードは、コンパイル時に実行されます。例えば、以下のマクロは、コンパイル時に現在の日時を表示します。
マクロ

このマクロを使用すると、以下のようにコードを記述できます。
マクロ

以上がCrystalのマクロの主な機能と構文の説明です。マクロを利用することで、より柔軟で効率的なコードを記述することができます。
= マクロの条件分岐と反復

Crystalのマクロは、条件分岐や反復処理を行うことができます。以下に、それぞれの構文と使用例を示します。
= マクロの条件分岐と反復

条件分岐は、if/elsif/elseやcase/when/elseのような構文を使用することができます。マクロ内では、条件分岐によって生成するコードを制御することができます。
= マクロの条件分岐と反復

反復処理は、whileやuntil、forのような構文を使用することができます。マクロ内では、反復処理によって生成するコードを制御することができます。
= マクロ p!

メソッド p は、与えられた「式」の inspaect() の返す値を puts しますが、マクロ p! は、それに先んじて(評価前の)「式」を表示します。
= マクロ p!

マクロ p! は入れ子に出来ます。また、一旦ASTに変換してから再度ソースコードに変換するので、等価な別の構文に変換されることがあります。