Skip to content

Latest commit

 

History

History
358 lines (241 loc) · 15.2 KB

content.adoc

File metadata and controls

358 lines (241 loc) · 15.2 KB

構文

著者: MakeNowJust

この章では 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 のようなメソッドではなく、構文として提供されていて、引数は固定の文字列ではなければいけないのです。

link:./examples/example1.rb[role=include]

後者を実感する例としては、こんなものがあります。 これは Crystal のプログラムです。

メソッドの引数に型を指定しているところに注目してください。 引数の型でメソッドをオーバーロードできます。 これは Ruby ではできません。

link:./examples/example2.cr[role=include]

それでは、 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 にはあります。

これは引数として与えられた式の結果についた型を返す構文です。 引数の式はコンパイル時にのみ利用され、実行時には利用されないことに注意してください。

link:./examples/typeof.cr[role=include]

リテラル

リテラル関連で Ruby と大きく異なるところは、次のものが挙げられます。

  • 数値リテラルの型指定

  • 空の配列と空のハッシュに対する型指定

  • タプルと名前付きタプル

逆に、これ以外は一部の例外を除いて Ruby と同じように書くことができます。

Note

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

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

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

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

数値リテラルの型指定

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

もちろん数値リテラルの末尾に何も指定しないことも可能で、その場合は整数なら 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 は反対に、一致するメソッドが複数見つかってしまった場合に使う機能です。 この場合は、まず一番最後に定義されたものが呼び出されます。 そして、その中で 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]

struct

class とよく似たものとして struct があります。

structclass とほぼ同等の機能を持っていますが、メモリ確保に違いがあります。 class で定義した型のインスタンスはヒープに置かれますが、 struct はスタックに置かれます。 このため struct の方が高速にインスタンスを作ることができます。

しかし、 struct は自分自身をインスタンス変数に持つことができないという制約があります。

enum

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
  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/