03. 構文
この章では Crystal の構文について説明します。
Crystal の構文を全て説明すると膨大になってしまうので、ここでは Ruby との違いに焦点を置いて解説したいと思います。
というのも、 Crystal の構文は Ruby の影響を強く受けており、多くの場合 Ruby のように書くことで Crystal のプログラムを書くことができます。
しかし、やはり Crystal と Ruby は別のプログラミング言語であり、構文の異なる部分もいくつか存在します。
既に知識があるのであれば、その違いを抑えていくのが Crystal の構文を理解する手助けになるでしょう。
Ruby の構文については次のサイトを参考にしてください。
- オブジェクト指向スクリプト言語 Ruby リファレンスマニュアル
また、Crystal の完全な構文は、公式サイトにある次のドキュメントを参考にしてください。
- Syntax and Semantics
Crystal と Ruby の違い
Crystal と Ruby は構文こそよく似ていますが、言語としては次のような大きな違いがあります。
-
Ruby はインタープリタで実行されるが、 Crystal はコンパイルして実行する。
-
Ruby には変数に型が無いが、 Crystal には型がある。
そして、この2が Crystal と Ruby の構文に違いをもたらしています。
前者を実感する例としては、こんなものがあります。
これは有効な Ruby のプログラムですが、 Crystal ではコンパイルエラーになります。
Crystal は require
で読み込むファイルまで含めてコンパイルしなければなりません。
なので require
は Ruby のようなメソッドではなく、構文として提供されていて、引数は固定の文字列ではなければいけないのです。
# frozen_string_literal: true
# Ruby のプログラム!
Dir['foo/*.rb'].sort.each do |file|
require file
end
後者を実感する例としては、こんなものがあります。
これは Crystal のプログラムです。
メソッドの引数に型を指定しているところに注目してください。
引数の型でメソッドをオーバーロードできます。
これは Ruby ではできません。
# Crystal のプログラム!
# `foo` の引数が `String` の場合のメソッド
def foo(x : String)
p :string
end
# `foo` の引数が `Int32` (数値)の場合のメソッド
def foo(x : Int32)
p :int32
end
# 呼び出し時に引数の型によって適切なものが選択される
foo "string"
foo 42
# output:
# :string
# :int32
それでは、 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)
-
こんなものがあるんだな、となんとなく覚えておいてください。
typeof
Ruby には無い構文として typeof
というものが Crystal にはあります。
これは引数として与えられた式の結果についた型を返す構文です。
引数の式はコンパイル時にのみ利用され、実行時には利用されないことに注意してください。
# 文字列リテラルの型は `String`
typeof("foo") # => String
# 引数の式は実行されない
typeof(puts "hello") # => Nil
# hello とは出力されない
リテラル
リテラル関連で Ruby と大きく異なるところは、次のものが挙げられます。
-
数値リテラルの型指定
-
空の配列と空のハッシュに対する型指定
-
タプルと名前付きタプル
逆に、これ以外は一部の例外を除いて Ruby と同じように書くことができます。
Note
|
一部の例外としては次のものが挙げられます。
|
数値リテラルの型指定
Ruby の数値は基本的には Integer
と Float
だけです。
しかし、Crystal の数値はその大きさや符号の有無によって Int32
・ Int64
・ UInt32
・ Float32
・ Float64
などが存在します。
Int32
は32ビットの符号付き整数型で、 UInt32
は32ビットの符号無し整数型、 Float64
は64ビットの浮動小数点型です。
そして、数値リテラルの末尾に i32
・ i64
・ f32
・ f64
などと付けることによって、値の型を指定できます。
もちろん数値リテラルの末尾に何も指定しないことも可能で、その場合は整数なら Int32
型、小数なら Float64
型になります。
# 何も指定していない数値は `Int32` 、 `Float64` 型
typeof(123) # => Int32
typeof(3.14) # => Float64
# 符号付き整数型
[typeof(123i8), typeof(123i16), typeof(123i32), typeof(123i64), typeof(123i128)]
# => [Int8, Int16, Int32, Int64, Int128]
# 符号無し整数型
[typeof(123u8), typeof(123u16), typeof(123u32), typeof(123u64), typeof(123u128)]
# => [UInt8, UInt16, UInt32, UInt64, UInt128]
# 浮動小数点型
[typeof(3.14f32), typeof(3.14f64)] # => [Float32, Float64]
空の配列と空のハッシュに対する型指定
空の配列は []
、空のハッシュは {}
のように書けますが、これだと要素やキーの型が分からないためコンパイルできません。
そこで Crystal では、空のリテラルに of 型
と続けることで型を指定します。
ハッシュの場合は of キーの型 ⇒ 値の型
になります。
# 要素の型が `Int32` の空の配列を作成
typeof([] of Int32) # => Array(Int32)
# キーの型が `String` 、要素の型が `Int32` の空のハッシュを作成
typeof({} of String => Int32) # => Hash(String, Int32)
タプルと名前付きタプル
これは可変長引数、名前付き引数と関連の深い概念なので、そこで説明します。
変数
変数名は小文字から始めなければならず、定数は大文字から始めねけらばいけません。
インスタンス変数は @
から、クラス変数は @@
から始めなければいけません。
これらは Ruby と同様です。
しかし Crystal にグローバル変数はありません。
代わりにクラス変数や定数を使ってください。
クラス・メソッド
クラス・メソッド関連で Ruby と大きく異なるところは、次のものが挙げられます。
-
メソッドの型指定・オーバーロード・
previous_def
-
名前付き引数
-
可変長引数の扱い
-
インスタンス変数の型
-
struct
また、 Crystal ではコンパイル時に全てのメソッドが定義されていなければいけません。
なので Ruby の define_method
のようなことはできません。
メソッドの型指定・オーバーロード・ previous_def
メソッドの型指定・オーバーロードは前述しましたが、異なる引数の型を持った同名のメソッドを定義すると、呼び出し時に適切なものが選択される、という機能です。
また、このときに引数の型が一致するメソッドが見つからなかった場合、コンパイルエラーになります。
previous_def
は反対に、一致するメソッドが複数見つかってしまった場合に使う機能です。
この場合は、まず一番最後に定義されたものが呼び出されます。
そして、その中で previous_def
を使うと、次に定義されたものが呼び出されるのです。
ちなみに、引数の型は指定しないこともできます。
その場合は任意の型を受け取ることになります。
ですが、実際に呼び出された引数が持っていないメソッドを呼び出していた場合は、コンパイルエラーになります。
# 最初に定義したもの
def foo(x : Int32)
1 + x
end
# 次に定義したもの
# こちらが呼ばれる
def foo(x : Int32)
previous_def + 2 # ここの `previous_def` で上の `foo` が呼ばれる
end
foo(3) # => 6
名前付き引数
名前付き引数とは、 Ruby ではキーワード引数と呼ばれるものです。
Crystal では、全ての引数を名前付き引数として呼び出すことができます。
他にも、名前付き引数として指定するための名前と、実際に引数として受け取る変数の名前を分けることができます。
これは、名前付き引数の名前として予約語を使いたいときに便利です。
次のようなメソッドを定義した場合、
# 割り算をするメソッド
def div(left, right)
left / right
end
# 引数名を空白で二つ並べると、
# 一つ目が名前付き引数用の名前、二つ目が内部で使う引数の名前になる
def even(if cond)
cond
end
このように名前付き引数を使ってメソッドを呼び出せます。
# 名前付き引数を指定
div(left: 1.0, right: 2.0) # => 0.5
# 順番を逆にして指定
div(right: 2.0, left: 1.0) # => 0.5
# `cond` ではなく `if` を名前付き引数に指定する
even if: true # => true
可変長引数
Ruby 同様、引数名の前に *
を置くと可変長引数を受け取るものとして、 **
を置くと名前付き引数の余った引数を受け取るものとしてマークされます。
Ruby では可変長引数は配列を、キーワード引数の余りはハッシュを受け取ります。
ですが、 Crystal では可変長引数ではタプル( Tuple
) に、名前付き引数の余りは名前付きタプル( NamedTuple
)になります。
タプルは配列に似ていますが、固定長・変更不可であり、各要素の型を保持しているのが特徴です。
また名前付きタプルもキーがシンボルのハッシュに似た型で、タプルと同様に変更不可で各シンボルのキーに対応する型を保持しているのが特徴です。
splat 展開の際にも、これらを渡します。
splat 展開も Ruby と同様の構文で、メソッド呼び出しで引数の前に *
を置いたものが可変長引数の splat 展開となります。また、引数の前に **
を置いたものは名前付き引数の splat 展開になります。
実際に可変長引数を受け取るメソッドの例です。
# 可変長引数・名前付き引数の余りを受け取ってその型を返すメソッド
def vararg_type(*tuple, **named_tuple)
[typeof(tuple), typeof(named_tuple)]
end
この実行結果は次のようになります。
# 可変長引数の場合
vararg_type(42, "str")
# => [Tuple(Int32, String), NamedTuple()]
# 名前付き引数の場合
vararg_type(foo: 42, bar: "str")
# => [Tuple(), NamedTuple(foo: Int32, bar: String)]
# splat 展開
tuple = {42, "str"} # タプルのリテラルは配列の `{ }` 版
# 名前付きタプルはハッシュの `:` 区切り版
named_tuple = {
foo: 42,
bar: "str",
}
vararg_type(*tuple, **named_tuple)
# => [Tuple(Int32, String), NamedTuple(foo: Int32, bar: String)]
インスタンス変数の型
Crystal ではインスタンス変数・クラス変数の型がコンパイル時に決定できなければいけません。
initialize
メソッドの中でインスタンス変数に代入している場合などは気を利かせて型を推論してくれます。
しかし、そうでない場合は型が分からずにコンパイルエラーになることがあります。
その場合は明示的にインスタンス変数の型を指定してください。
また、メソッドの引数名としてインスタンス変数を指定すると、メソッドの呼び出しと同時に、そのインスタンス変数に代入できます。
class Foo
# 引数にインスタンス変数を指定すると、その変数に代入される。
# 加えて、引数の型やデフォルト引数を指定すると、インスタンス変数の型が
# その型に推論される。
def initialize(@foo : Int32, @bar = "bar")
# こういう単純な代入の場合もインスタンス変数の型は推論される。
@baz = Array(Int32).new
end
# インスタンス変数に対するゲッターメソッドを定義
getter foo, bar, baz
end
foo = Foo.new 42
typeof(foo.foo) # => Int32
typeof(foo.bar) # => String
typeof(foo.baz) # => Array(Int32)
struct
class
とよく似たものとして struct
があります。
struct
も class
とほぼ同等の機能を持っていますが、メモリ確保に違いがあります。
class
で定義した型のインスタンスはヒープに置かれますが、 struct
はスタックに置かれます。
このため struct
の方が高速にインスタンスを作ることができます。
しかし、 struct
は自分自身をインスタンス変数に持つことができないという制約があります。
enum
Crystal には enum
があります。
これは連番の数値型に分かりやすい名前を付けたもので、さらにメソッドを定義することもできます。
そして、 @[Flags]
属性を付けると、単なる連番ではなく値はビットフラグになります。
また、enum
にはオートキャストという機能もあります。引数の型制約に enum
を指定して、シンボルが渡されたときに、自動でシンボルから enum
へ変換するというものです。シンボルが enum
として有効な値かどうかはコンパイル時にチェックされるので、普通にシンボルを使う場合よりも安全です。
# 色を表す `enum` を定義
enum Color
Red
Green
Blue
# 値を直接指定することもできる
Yellow = 100
end
def color_name(color : Color)
color
end
# ファイルの開く際にモードを表す `enum` を定義
@[Flags]
enum FileMode
Read
Write
end
# `enum` は定数と同じように `::` で参照する。
Color::Red # => Red
# オートキャストが発動して、 `:green` が `Color::Green` に変換される。
color_name(:green) # => Green
# `@[Flags]` のついた `enum` は `|` で複数指定できる。
FileMode::Read | FileMode::Write # => Read | Write
メソッド呼び出し
メソッド呼び出しの構文はほとんど Ruby と同じですが、1つだけ異なる点があります。
Crystal には一引数ブロックの省略記法というものがあります。
これは ブロックの第一引数に対してメソッドを呼び出す場合に &.メソッド名
のように書けるという構文です。
さらに、そこからメソッドチェインを始めることができるため、場合によってはとても便利です。
# `42` を `yield` するメソッド
def yield_42
yield 42
end
yield_42 { |x| x.to_s.size } # => 2
# ↑を次のように書き直せる
yield_42 &.to_s.size # => 2
条件分岐・繰り返し
条件分岐・繰り返しの構文は 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 ドキュメント