03. 構文

この章では Crystal の構文について説明します。

Crystal の構文を全て説明すると膨大になってしまうので、ここでは Ruby との違いに焦点を置いて解説したいと思います。
というのも、 Crystal の構文は Ruby の影響を強く受けており、多くの場合 Ruby のように書くことで Crystal のプログラムを書くことができます。
しかし、やはり Crystal と Ruby は別のプログラミング言語であり、構文の異なる部分もいくつか存在します。
既に知識があるのであれば、その違いを抑えていくのが Crystal の構文を理解する手助けになるでしょう。

Ruby の構文については次のサイトを参考にしてください。

オブジェクト指向スクリプト言語 Ruby リファレンスマニュアル

https://docs.ruby-lang.org/ja/latest/doc/index.html

また、Crystal の完全な構文は、公式サイトにある次のドキュメントを参考にしてください。

Crystal と Ruby の違い

Crystal と Ruby は構文こそよく似ていますが、言語としては次のような大きな違いがあります。

  1. Ruby はインタープリタで実行されるが、 Crystal はコンパイルして実行する。

  2. 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 (文字列)、 NilBool など

  • ジェネリックス

    • Array(Int32) (要素の型が Int32 の配列)

    • Array(String) (要素の型が String の配列)

    • Hash(String, Int32) (キーの型が String で値の型が Int32 のハッシュ)

  • ユニオン

    • Int32 | StringInt32String 型)

    • 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

一部の例外としては次のものが挙げられます。

  • いくつかの % 形式のリテラル( %s%W など)が存在しない。

  • ハッシュの {foo: bar} のような形式は名前付きタプルの構文となっている。

  • 正規表現の構文がPCREになっている。

数値リテラルの型指定

Ruby の数値は基本的には IntegerFloat だけです。
しかし、Crystal の数値はその大きさや符号の有無によって Int32Int64UInt32Float32Float64 などが存在します。
Int32 は32ビットの符号付き整数型で、 UInt32 は32ビットの符号無し整数型、 Float64 は64ビットの浮動小数点型です。
そして、数値リテラルの末尾に i32i64f32f64 などと付けることによって、値の型を指定できます。

もちろん数値リテラルの末尾に何も指定しないことも可能で、その場合は整数なら 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 があります。

structclass とほぼ同等の機能を持っていますが、メモリ確保に違いがあります。
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
  1. foo の型は String | Nil

  2. この位置に来たら foo は確実に String

  3. この位置に来たら foo は確実に Nil

ということが分かると思います。

このように、条件分岐の条件によって、ブロック内で変数の型がいい感じに変化するのです。

型のフィルタに使える特殊な構文には次のものがあります。

is_a?

foo.is_a?(String) は変数 fooString 型のとき true になります。

nil?

is_a?(Nil) の省略形です。

responds_to?

foo.responds_to?(:size) は変数 foo がメソッド size を持っているときに true になります。

ちなみにこれらの構文はメソッドのような見た目ですがメソッドではないため、オーバライドなどはできないことに注意してください。

また、これらを &&||! で組み合わせたものも動作します。

まとめ

説明しなかった構文はいくつもありますが、この辺りの構文を覚えておけば Crystal のソースコードがそれなりに読めるようになるはずです。

説明しなかったのは、

  • ジェネリックスの構文

  • C言語と連携のための構文

  • マクロ

  • アノテーション

などです。
これらは高度な機能なので、当分は知らなくても問題がないでしょう。

ちなみに、マクロについては次の章で詳しく解説されるはずです。

また、分からない構文はあれば最初に挙げた Crystal のドキュメントを確認してみるといいでしょう。
これよりも詳細に書かれているはずです。

加えて、 標準ライブラリの API ドキュメントは以下にあります。
知らない型やメソッドが出てきたときに確認してみてください。

API ドキュメント

https://crystal-lang.org/api/