著者: MakeNowJust
この章では Crystal の構文について説明します。
Crystal の構文を全て説明すると膨大になってしまうので、ここでは Ruby との違いに焦点を置いて解説したいと思います。 というのも、 Crystal の構文は Ruby の影響を強く受けており、多くの場合 Ruby のように書くことで Crystal のプログラムを書くことができます。 しかし、やはり Crystal と Ruby は別のプログラミング言語であり、構文の異なる部分もいくつか存在します。 既に知識があるのであれば、その違いを抑えていくのが Crystal の構文を理解する手助けになるでしょう。
Ruby の構文については次のサイトを参考にしてください。
- オブジェクト指向スクリプト言語 Ruby リファレンスマニュアル
また、Crystal の完全な構文は、公式サイトにある次のドキュメントを参考にしてください。
- Syntax and Semantics
Crystal と Ruby は構文こそよく似ていますが、言語としては次のような大きな違いがあります。
-
Ruby はインタープリタで実行されるが、 Crystal はコンパイルして実行する。
-
Ruby には変数に型が無いが、 Crystal には型がある。
そして、この2が Crystal と Ruby の構文に違いをもたらしています。
前者を実感する例としては、こんなものがあります。 これは有効な Ruby のプログラムですが、 Crystal ではコンパイルエラーになります。
Crystal は require
で読み込むファイルまで含めてコンパイルしなければなりません。
なので require
は Ruby のようなメソッドではなく、構文として提供されていて、引数は固定の文字列ではなければいけないのです。
link:./examples/example1.rb[role=include]
後者を実感する例としては、こんなものがあります。 これは Crystal のプログラムです。
メソッドの引数に型を指定しているところに注目してください。 引数の型でメソッドをオーバーロードできます。 これは Ruby ではできません。
link:./examples/example2.cr[role=include]
それでは、 Crystal と Ruby で構文の異なる部分を説明していきます。
これらの違いを意識しながら読み進めていってください。
はじめに Crystal の型システムについて簡単に説明しておきます。
Crystal の型には次のようなものがあります。
-
通常の型
-
Int32
(整数)やString
(文字列)、Nil
、Bool
など
-
-
ジェネリックス
-
Array(Int32)
(要素の型がInt32
の配列) -
Array(String)
(要素の型がString
の配列) -
Hash(String, Int32)
(キーの型がString
で値の型がInt32
のハッシュ)
-
-
ユニオン
-
Int32 | String
(Int32
かString
型) -
Int32?
(Int32 | Nil
の糖衣構文)
-
-
Proc
-
Int32 → String
(引数にInt32
を受け取ってString
を返す Proc)
-
こんなものがあるんだな、となんとなく覚えておいてください。
Ruby には無い構文として typeof
というものが Crystal にはあります。
これは引数として与えられた式の結果についた型を返す構文です。 引数の式はコンパイル時にのみ利用され、実行時には利用されないことに注意してください。
link:./examples/typeof.cr[role=include]
リテラル関連で Ruby と大きく異なるところは、次のものが挙げられます。
-
数値リテラルの型指定
-
空の配列と空のハッシュに対する型指定
-
タプルと名前付きタプル
逆に、これ以外は一部の例外を除いて Ruby と同じように書くことができます。
Note
|
一部の例外としては次のものが挙げられます。
|
Ruby の数値は基本的には Integer
と Float
だけです。
しかし、Crystal の数値はその大きさや符号の有無によって Int32
・ Int64
・ UInt32
・ Float32
・ Float64
などが存在します。
Int32
は32ビットの符号付き整数型で、 UInt32
は32ビットの符号無し整数型、 Float64
は64ビットの浮動小数点型です。
そして、数値リテラルの末尾に i32
・ i64
・ f32
・ f64
などと付けることによって、値の型を指定できます。
もちろん数値リテラルの末尾に何も指定しないことも可能で、その場合は整数なら Int32
型、小数なら Float64
型になります。
link:./examples/number.cr[role=include]
空の配列は []
、空のハッシュは {}
のように書けますが、これだと要素やキーの型が分からないためコンパイルできません。
そこで Crystal では、空のリテラルに of 型
と続けることで型を指定します。
ハッシュの場合は of キーの型 ⇒ 値の型
になります。
link:./examples/empty.cr[role=include]
変数名は小文字から始めなければならず、定数は大文字から始めねけらばいけません。
インスタンス変数は @
から、クラス変数は @@
から始めなければいけません。
これらは Ruby と同様です。
しかし Crystal にグローバル変数はありません。 代わりにクラス変数や定数を使ってください。
クラス・メソッド関連で Ruby と大きく異なるところは、次のものが挙げられます。
-
メソッドの型指定・オーバーロード・
previous_def
-
名前付き引数
-
可変長引数の扱い
-
インスタンス変数の型
-
struct
また、 Crystal ではコンパイル時に全てのメソッドが定義されていなければいけません。
なので Ruby の define_method
のようなことはできません。
メソッドの型指定・オーバーロードは前述しましたが、異なる引数の型を持った同名のメソッドを定義すると、呼び出し時に適切なものが選択される、という機能です。
また、このときに引数の型が一致するメソッドが見つからなかった場合、コンパイルエラーになります。
previous_def
は反対に、一致するメソッドが複数見つかってしまった場合に使う機能です。
この場合は、まず一番最後に定義されたものが呼び出されます。
そして、その中で previous_def
を使うと、次に定義されたものが呼び出されるのです。
ちなみに、引数の型は指定しないこともできます。 その場合は任意の型を受け取ることになります。 ですが、実際に呼び出された引数が持っていないメソッドを呼び出していた場合は、コンパイルエラーになります。
link:./examples/previous_def.cr[role=include]
名前付き引数とは、 Ruby ではキーワード引数と呼ばれるものです。
Crystal では、全ての引数を名前付き引数として呼び出すことができます。
他にも、名前付き引数として指定するための名前と、実際に引数として受け取る変数の名前を分けることができます。 これは、名前付き引数の名前として予約語を使いたいときに便利です。
次のようなメソッドを定義した場合、
link:./examples/named_arg.cr[role=include]
このように名前付き引数を使ってメソッドを呼び出せます。
link:./examples/named_arg.cr[role=include]
Ruby 同様、引数名の前に *
を置くと可変長引数を受け取るものとして、 **
を置くと名前付き引数の余った引数を受け取るものとしてマークされます。
Ruby では可変長引数は配列を、キーワード引数の余りはハッシュを受け取ります。
ですが、 Crystal では可変長引数ではタプル( Tuple
) に、名前付き引数の余りは名前付きタプル( NamedTuple
)になります。
タプルは配列に似ていますが、固定長・変更不可であり、各要素の型を保持しているのが特徴です。 また名前付きタプルもキーがシンボルのハッシュに似た型で、タプルと同様に変更不可で各シンボルのキーに対応する型を保持しているのが特徴です。
splat 展開の際にも、これらを渡します。
splat 展開も Ruby と同様の構文で、メソッド呼び出しで引数の前に *
を置いたものが可変長引数の splat 展開となります。また、引数の前に **
を置いたものは名前付き引数の splat 展開になります。
実際に可変長引数を受け取るメソッドの例です。
link:./examples/vararg.cr[role=include]
この実行結果は次のようになります。
link:./examples/vararg.cr[role=include]
Crystal ではインスタンス変数・クラス変数の型がコンパイル時に決定できなければいけません。
initialize
メソッドの中でインスタンス変数に代入している場合などは気を利かせて型を推論してくれます。
しかし、そうでない場合は型が分からずにコンパイルエラーになることがあります。
その場合は明示的にインスタンス変数の型を指定してください。
また、メソッドの引数名としてインスタンス変数を指定すると、メソッドの呼び出しと同時に、そのインスタンス変数に代入できます。
link:./examples/infer_ivar.cr[role=include]
Crystal には enum
があります。
これは連番の数値型に分かりやすい名前を付けたもので、さらにメソッドを定義することもできます。
そして、 @[Flags]
属性を付けると、単なる連番ではなく値はビットフラグになります。
また、enum
にはオートキャストという機能もあります。引数の型制約に enum
を指定して、シンボルが渡されたときに、自動でシンボルから enum
へ変換するというものです。シンボルが enum
として有効な値かどうかはコンパイル時にチェックされるので、普通にシンボルを使う場合よりも安全です。
link:./examples/enum.cr[role=include]
メソッド呼び出しの構文はほとんど Ruby と同じですが、1つだけ異なる点があります。
Crystal には一引数ブロックの省略記法というものがあります。
これは ブロックの第一引数に対してメソッドを呼び出す場合に &.メソッド名
のように書けるという構文です。
さらに、そこからメソッドチェインを始めることができるため、場合によってはとても便利です。
link:./examples/call.cr[role=include]
条件分岐・繰り返しの構文は Ruby とほとんど同じです。
ただし redo
はありません。
条件分岐の条件に変数が対象になっている場合、その変数の型がフィルタされます。
例えば、次のコードを考えてみましょう。
foo = rand > 0.5 ? "foo" : nil
# (1)
if foo
# (2)
else
# (3)
end
-
foo
の型はString | Nil
-
この位置に来たら
foo
は確実にString
型 -
この位置に来たら
foo
は確実にNil
型
ということが分かると思います。
このように、条件分岐の条件によって、ブロック内で変数の型がいい感じに変化するのです。
型のフィルタに使える特殊な構文には次のものがあります。
is_a?
-
foo.is_a?(String)
は変数foo
がString
型のときtrue
になります。 nil?
-
is_a?(Nil)
の省略形です。 responds_to?
-
foo.responds_to?(:size)
は変数foo
がメソッドsize
を持っているときにtrue
になります。
ちなみにこれらの構文はメソッドのような見た目ですがメソッドではないため、オーバライドなどはできないことに注意してください。
また、これらを &&
や ||
や !
で組み合わせたものも動作します。
説明しなかった構文はいくつもありますが、この辺りの構文を覚えておけば Crystal のソースコードがそれなりに読めるようになるはずです。
説明しなかったのは、
-
ジェネリックスの構文
-
C言語と連携のための構文
-
マクロ
-
アノテーション
などです。 これらは高度な機能なので、当分は知らなくても問題がないでしょう。
ちなみに、マクロについては次の章で詳しく解説されるはずです。
また、分からない構文はあれば最初に挙げた Crystal のドキュメントを確認してみるといいでしょう。 これよりも詳細に書かれているはずです。
- Syntax and Semantics
加えて、 標準ライブラリの API ドキュメントは以下にあります。 知らない型やメソッドが出てきたときに確認してみてください。
- API ドキュメント