コンテンツへスキップ

RubyistのためのCrystal

CrystalはRubyのような構文を持っていますが、Crystalは別のRuby実装ではなく、異なる言語です。この理由から、そして主にコンパイルされた静的型付き言語であるため、Rubyと比較するといくつかの大きな違いがあります。

コンパイル言語としてのCrystal

crystalコマンドの使用

foo.crというプログラムがある場合

# Crystal
puts "Hello world"

これらのコマンドのいずれかを実行すると、同じ出力が得られます。

$ crystal foo.cr
Hello world
$ ruby foo.cr
Hello world

crystalがファイルを解釈しているように見えますが、実際にはファイルfoo.crは最初に一時的な実行可能ファイルにコンパイルされ、その後この実行可能ファイルが実行されます。この動作は、通常ファイルをコンパイルしてすぐに実行したい場合に、開発サイクルで非常に役立ちます。

コンパイルだけしたい場合は、buildコマンドを使用できます。

$ crystal build foo.cr

これにより、fooという実行可能ファイルが作成され、./fooで実行できます。

これは最適化されていない実行可能ファイルを作成することに注意してください。最適化するには、--releaseフラグを渡します。

$ crystal build foo.cr --release

ベンチマークを作成したり、パフォーマンスをテストしたりする場合は、常にリリースモードでコンパイルすることを忘れないでください。

引数なしでcrystalを呼び出すか、コマンドと引数なしでcrystalを呼び出す(たとえば、crystal buildはそのコマンドで使用できるすべてのフラグをリストします)ことで、他のコマンドとフラグを確認できます。または、マニュアルを読むこともできます。

Bool

truefalseは、クラスTrueClassまたはFalseClassのインスタンスではなく、Bool型です。

整数

RubyのFixnum型には、Crystalの整数型Int8Int16Int32Int64UInt8UInt16UInt32、またはUInt64のいずれかを使用します。

RubyのFixnumに対する操作がその範囲を超えると、値は自動的にBignumに変換されます。Crystalは代わりにオーバーフロー時にOverflowErrorを発生させます。例:

x = 127_i8 # An Int8 type
x          # => 127
x += 1     # Unhandled exception: Arithmetic overflow (OverflowError)

Crystalの標準ライブラリは、任意のサイズと精度の数値型を提供します。BigDecimalBigFloatBigIntBigRational

言語リファレンスの整数を参照してください。

正規表現

グローバル変数$`$'はサポートされていません(まだ$~$1$2、...は存在します)。$~.pre_match$~.post_matchを使用してください。詳細はこちら

削減されたインスタンスメソッド

Rubyでは同じことをするためのメソッドがいくつかある場合、Crystalでは1つしかない場合があります。具体的には

Rubyメソッド Crystalメソッド
Enumerable#detect Enumerable#find
Enumerable#collect Enumerable#map
Object#respond_to? Object#responds_to?
lengthsizecount size

省略された言語構造

Rubyにはいくつかの代替構造がある場合、Crystalには1つあります。

  • 後置のwhile/untilはありません。ただし、接尾辞としてのifはまだ利用可能です。
  • andor: 代わりに&&||を使用し、優先順位を示すために適切な括弧を使用します。
  • RubyにはKernel#procKernel#lambdaProc#new->がありますが、CrystalではProc(*T, R).new->を使用します(参照についてはこちらを参照してください)。
  • require_relative "foo"の場合は、require "./foo"を使用します。

配列の自動スプラットと強制された最大ブロック引数

[[1, "A"], [2, "B"]].each do |a, b|
  pp a
  pp b
end

次のようなエラーメッセージが生成されます。

    in line 1: too many block arguments (given 2, expected maximum 1)

ただし、不要な引数を省略しても問題ありません(Rubyの場合と同様)。例:

[[1, "A"], [2, "B"]].each do # no arguments
  pp 3
end

または

def many
  yield 1, 2, 3
end

many do |x, y| # ignoring value passed in for "z" is OK
  puts x + y
end

タプルには自動スプラットがあります。

[{1, "A"}, {2, "B"}].each do |a, b|
  pp a
  pp b
end

期待どおりの結果が返されます。

Rubyの自動スプラットと同じ結果を得るために、明示的にアンパックすることもできます。

[[1, "A"], [2, "B"]].each do |(a, b)|
  pp a
  pp b
end

次のコードも機能しますが、前者をお勧めします。

[[1, "A"], [2, "B"]].each do |e|
  pp e[0]
  pp e[1]
end

#eachはnilを返す

Rubyでは、.eachArrayHashなどの多くの組み込みコレクションに対してレシーバーを返します。これにより、そのメソッドを連鎖させることができますが、Crystalではパフォーマンスとコード生成の問題につながる可能性があるため、この機能はサポートされていません。代わりに、.tapを使用できます。

Ruby

[1, 2].each { "foo" } # => [1, 2]

Crystal

[1, 2].each { "foo" }       # => nil
[1, 2].tap &.each { "foo" } # => [1, 2]

参考

リフレクションと動的評価

Kernel#eval()と奇妙なKernel#autoload()は省略されています。オブジェクトとクラスのイントロスペクションメソッドObject#kind_of?()Object#methodsObject#instance_methods、およびClass#constantsも省略されています。

場合によっては、マクロをリフレクションに使用できます。

意味的な違い

シングルクォートとダブルクォートの文字列

Rubyでは、文字列リテラルはシングルクォートまたはダブルクォートで区切ることができます。Rubyのダブルクォートで囲まれた文字列は、リテラル内部で変数展開が行われますが、シングルクォートで囲まれた文字列は行われません。

Crystalでは、文字列リテラルはダブルクォートのみで区切られます。シングルクォートは、C言語のような文字リテラルとして機能します。Rubyと同様に、文字列リテラル内では変数展開が行われます。

まとめ

X = "ho"
puts '"cute"' # Not valid in crystal, use "\"cute\"", %{"cute"}, or %("cute")
puts "Interpolate #{X}"  # works the same in Ruby and Crystal.

RubyやPythonのトリプルクォートで囲まれた文字列リテラルはサポートされていませんが、文字列リテラルには改行を埋め込むことができます。

"""Now,
what?""" # Invalid Crystal use:
"Now,
what?"  # Valid Crystal

Crystalは多くのパーセント文字列リテラルをサポートしています。

[][]?メソッド

Rubyでは、[]メソッドは一般的に、そのインデックス/キーによる要素が見つからない場合、nilを返します。例として

# Ruby
a = [1, 2, 3]
a[10] #=> nil

h = {a: 1}
h[1] #=> nil

Crystalでは、このような場合、例外がスローされます。

# Crystal
a = [1, 2, 3]
a[10] # => raises IndexError

h = {"a" => 1}
h[1] # => raises KeyError

この変更の背景にある理由は、すべてのArrayまたはHashへのアクセスが潜在的な値としてnilを返す可能性がある場合、この方法でプログラミングするのが非常に面倒になるからです。これでは機能しません。

# Crystal
a = [1, 2, 3]
a[0] + a[1] # => Error: undefined method `+` for Nil

インデックス/キーが見つからない場合にnilを取得したい場合は、[]?メソッドを使用できます。

# Crystal
a = [1, 2, 3]
value = a[4]? # => return a value of type Int32 | Nil
if value
  puts "The number at index 4 is : #{value}"
else
  puts "No number at index 4"
end

[]?は、コンテナのようなクラスに対して定義できる(そして定義すべき)通常のメソッドです。

もう一つ知っておくべきことは、次のような場合

# Crystal
h = {1 => 2}
h[3] ||= 4

プログラムは実際には次のように変換されるということです。

# Crystal
h = {1 => 2}
h[3]? || (h[3] = 4)

つまり、[]?メソッドは、インデックス/キーの存在を確認するために使用されます。

[]nilを返さないのと同様に、一部のArrayおよびHashメソッドもnilを返さず、要素が見つからない場合は例外を発生させます。firstlastshiftpopなど。これらには、nilの動作を取得するための疑問符付きメソッドも用意されています。first?last?shift?pop?など。


慣例では、obj[key]は値を返すか、keyがない場合は例外を発生させ(「ない」の定義はobjのタイプによって異なります)、obj[key]?は値を返すか、keyがない場合はnilを返します。

他のメソッドについては、異なります。同じタイプにfooという名前のメソッドと別のfoo?がある場合、それはfooが何らかの条件で例外を発生させ、foo?が同じ条件でnilを返すことを意味します。foo?バリアントのみが存在し、fooが存在しない場合は、真または偽の値(必ずしもtrueまたはfalseではない)を返します。

上記のすべての例

  • Array#[](index)は範囲外で例外を発生させ、Array#[]?(index)はその場合にnilを返します。
  • Hash#[](key)はキーがハッシュにない場合に例外を発生させ、Hash#[]?(key)はその場合にnilを返します。
  • Array#firstは配列が空の場合に例外を発生させ(「first」がないため、「first」がない)、Array#first?はその場合にnilを返します。pop/pop?、shift/shift?、last/last?についても同様です。
  • String#includes?(obj)Enumerable#includes?(obj)Enumerable#all?がありますが、これらには疑問符なしのバリアントはありません。前のメソッドは確かにtrueまたはfalseを返しますが、それは必要な条件ではありません。

forループ

forループはサポートされていません。代わりに、Enumerable#eachを使用することをお勧めします。それでもforが必要な場合は、マクロを介して追加できます。

macro for(expr)
  {{expr.args.first.args.first}}.each do |{{expr.name.id}}|
    {{expr.args.first.block.body}}
  end
end

for i  [1, 2, 3] do # You can replace ∈ with any other word or character, just not `in`
  puts i
end
# note the trailing 'do' as block-opener!

メソッド

Rubyでは、以下は引数エラーを発生させます。

def process_data(a, b)
  # do stuff...
end

process_data(b: 2, a: "one")

これは、Rubyでは、process_data(b: 2, a: "one")process_data({b: 2, a: "one"})の構文糖であるためです。

Crystalでは、コンパイラはprocess_data(b: 2, a: "one")を、名前付き引数b: 2a: "one"を使用してprocess_dataを呼び出すものとして扱います。これは、process_data("one", 2)と同じです。

プロパティ

Rubyのattr_accessorattr_reader、およびattr_writerメソッドは、異なる名前のマクロに置き換えられています。

Rubyキーワード Crystal
attr_accessor property
attr_reader getter
attr_writer setter

getter :name, :bday

さらに、Crystalはnil可能またはブール値のインスタンス変数用のアクセサマクロを追加しました。それらの名前には疑問符(?)が含まれています。

Crystal
property?
getter?

class Person
  getter? happy = true
  property? sad = true
end

p = Person.new

p.sad = false

puts p.happy?
puts p.sad?

これはブール値用ですが、任意の型を指定できます。

class Person
  getter? feeling : String = "happy"
end

puts Person.new.feeling?
# => happy

ドキュメントのgetter?および/またはproperty?について詳しくお読みください。

一貫したドット表記法

たとえば、RubyのFile::exists?は、CrystalではFile.exists?になります。

Crystalキーワード

Crystalはいくつかの新しいキーワードを追加しました。これらはメソッド名として使用できますが、ドットを使用して明示的に呼び出す必要があります。例:self.select { |x| x > "good" }

利用可能なキーワード

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

プライベートメソッド

Crystalでは、各プライベートメソッドにprivateキーワードを付ける必要があります。

private def method
  42
end

RubyからCrystalへのハッシュ構文

Crystalは、Rubyでは利用できないデータ型、NamedTupleを導入します。

通常、Rubyでは、いくつかの構文でハッシュを定義できます。

# A valid ruby hash declaration
{ 
  key1: "some value",
  some_key2: "second value"
}

# This syntax in ruby is shorthand for the hash rocket => syntax
{
  :key1 => "some value",
  :some_key2 => "second value"
}

Crystalでは、そうではありません。Crystalでハッシュを宣言するには、Hashロケット=>構文が必要です。

ただし、RubyのHashショートハンド構文は、CrystalでNamedTupleを作成します。

# Creates a valid `Hash(Symbol, String)` in Crystal
{
  :key1      => "some value",
  :some_key2 => "second value",
}

# Creates a `NamedTuple(key1: String, some_key2: String)` in Crystal
{
  key1:      "some value",
  some_key2: "second value",
}

NamedTupleと通常のTupleはサイズが固定されているため、コンパイル時に既知のデータ構造に最適です。

疑似定数

Crystalは、実行中のソースコードに関するリフレクティブデータを提供するいくつかの疑似定数を提供します。

Crystalドキュメントの疑似定数について詳しくお読みください。

Crystal Ruby 説明
__FILE__ __FILE__ 現在実行中のcrystalファイルへのフルパス。
__DIR__ __dir__ 現在実行中のcrystalファイルが配置されているディレクトリへのフルパス。
__LINE__ __LINE__ 現在実行中のcrystalファイルの現在の行番号。
__END_LINE__ - 呼び出しブロックの終了行の行番号。メソッドパラメータのデフォルト値としてのみ使用できます。

Ruby Gems用のCrystal Shards

多くの人気のあるRuby gemが、Crystalに移植または書き直されています。Ruby Gemsの同等のCrystal Shardsを以下に示します


RubyとCrystalの違いに関するその他の質問については、FAQをご覧ください。