Wikipedia
ウィキペディアCrystal (プログラミング言語)の記事があります。

本書は、Crystalのチュートリアルです。 Crystalは、Ary Borenszweig、Juan Wajnerman、Brian Cardiffと300人以上の貢献者によって設計・開発された汎用オブジェクト指向プログラミング言語です[1]Ruby にヒントを得た構文を持ち、静的型付けコンパイル型言語ですが、変数やメソッドの引数の型は一般には不要です。型は高度なグローバル型推論アルゴリズムによって解決されます。[2]CrystalはApache Licenseバージョン2.0のもと、FOSSとしてリリースされています。

Hello, World!編集

お約束のHello_worldですが、ここではRubyとの比較も兼ねて、Ruby#Hello, World!をそのまま実行してみます。

hello.crを用意します[3]

hello.cr
puts 'Hello, World!'
コマンドラインでの操作
% cat hello.cr 
puts 'Hello, World!'
% crystal hello.cr
In hello.cr:1:6

 1 | puts 'Hello, World!'
          ^
Error: unterminated char literal, use double quotes for strings

% sed -i -e "s@'@Q@g" -e 's@Q@"@g' hello.cr
% cat hello.cr 
puts "Hello, World!"
% crystal hello.cr
Hello, World!
Crystalでは、文字列の場合は二重引用符(")を使用するので、' を " に sed で置換えました。
修正後の hello.cr も問題なく ruby で実行できます。

プログラミング環境編集

Crystalのプログラムを作り、コンパイル・実装するには、「オンライン実行環境を使う」・「エディト・コンパイル・実行環境を用意してそれを使う」の2通りの方法があります。

オンライン実行環境編集

公式のオンライン実行環境、 https://play.crystal-lang.org/ があります。 まずは、これを使って本書に例示されているコードを実行してみることをお勧めします。

エディト・コンパイル・実行環境編集

エディタについては本書では触れませんが、プログラミング時間の大半はエディタの操作に費やされるため、良いエディタを選択することが重要です。

Crystal の言語処理系は、 https://crystal-lang.org/install/ から入手します。

自分の、OSやGNU/Linuxであればディストリビューションに合わせてインストールしてください。 また、FreeBSDのように crystal と shards が別パッケージとなっていることもあるので、その場合は shards も追加インストールします。

ソースコードからのビルド編集

多くの場合、インストールされた crystal はスタティック リンクされているので、ダイナミック リンク版の crystal を入手するには、ソースコードからビルドします。 また、interactive Crystalを有効にするためにも、ソースコードからのビルドが必要です。

crystal は、ソースコードが Github の https://github.com/crystal-lang/crystal.git で公開されているので、必要に応じてソースコードからビルドすることができます。 crystalは、セルフホスティング言語[4]なので、最初にバイナリーを入手してブートストラップするか、クロスビルドしたバイナリーを持込むか、パッケージシステムからインストールし、ターゲットでセルフコンパイル出来る状態を作る方法があります。

ビルドには、Chromebook(メモリー4GB, Celeron N4000, OS Version: reven-release/R104-14909.124.0, Chromebrew version: `1.25.3`, llvm-15.0.0)で約30分かかりました。

crystal コマンド編集

crystal コマンドは Crystal のコンパイラであると同時に、ビルドツールなどを含んだツールチェインです(プログラミング言語のCrystalは、先頭を大文字、コマンドのcrystalは、先頭を小文字にして区別します)。

[TODO: コマンドラインツール crystal の解説。 crystal ファイル名 は crystal run ファイル名 の短縮形で、インタープリタ的な実行…ではなく、内部ビルドツールでコンパイル・実行を行う]

Ruby との違い編集

Crystalは、Rubyに触発された構文を持つものの、Rubyとの互換性をゴールに定めてはいません。 このため、細部を見ると仕様に差異があり、Rubyのソースコードをcrystalに掛けても前節の 'Hello World' の様にコンパイルに失敗することがあります。 また、コンパイルできても実行結果に違いが出ることがあります。

ここでは、Ruby との違いについて実際のコードと双方の結果を比較することで、差異についての理解を深めていきます。

整数型の特性編集

大きな整数
p 2 ** 999
p (2 ** 999).class
rubyの実行結果
5357543035931336604742125245300009052807024058527668037218751941851755255624680612465991894078479290637973364587765734125935726428461570217992288787349287401967283887412115492710537302531185570938977091076523237491790970633699383779582771973038531457285598238843271083830214915826312193418602834034688
Integer
crystalの実行結果
Unhandled exception: Arithmetic overflow (OverflowError)
  from /usr/local/share/crystal/share/crystal/src/int.cr:295:9 in '**'
  from pow.cr:1:1 in '__crystal_main'
  from /usr/local/share/crystal/share/crystal/src/crystal/main.cr:115:5 in 'main_user_code'
  from /usr/local/share/crystal/share/crystal/src/crystal/main.cr:101:7 in 'main'
  from /usr/local/share/crystal/share/crystal/src/crystal/main.cr:127:3 in 'main'
  from /usr/local/lib64/libc.so.6 in '__libc_start_main'
  from /usr/local/.cache/crystal/crystal-run-pow.tmp in '_start'
  from ???
Ruby の整数は、桁あふれが起こると自動的に多倍長整数に型変換されるので、継ぎ目なしに大きな数を扱うアルゴルズムが使えます。
Crystal の整数は、固定長です(大きさについては後述)。なので大きな答えになる式を評価すると桁あふれが生じます。桁あふれが生じますが、C言語のように寡黙に処理を続けるのではなく、実行時に例外(OverflowError)が上がるので、例外を捕捉し然るべき処置を施すことが可能です。

BigInt編集

bigrequire すると BigInt が使えるようになります。

BigInt
require "big"

p BigInt.new(2) ** 999
p (BigInt.new(2) ** 999).class
実行結果
5357543035931336604742125245300009052807024058527668037218751941851755255624680612465991894078479290637973364587765734125935726428461570217992288787349287401967283887412115492710537302531185570938977091076523237491790970633699383779582771973038531457285598238843271083830214915826312193418602834034688
BigInt
BigIntはプリミティブではなので、リテラル表現はありません。また、
n : BigInt = 2
Error: type must be BigInt, not Int32
のように型アノテーションすることも出来ません。

リテラルと型編集

様々なリテラルと型
[nil, false, true, 42, 2.73, 'Q', "string", [1,2,3], {a:1, b:2}].each{|x|
  p [x, x.class]
}
rubyの実行結果
[nil, NilClass]
[false, FalseClass]
[true, TrueClass]
[42, Integer]
[2.73, Float]
["Q", String]
["string", String]
[[1, 2, 3], Array] 
[{:a=>1, :b=>2}, Hash]
crystalの実行結果
[nil, Nil]
[false, Bool]
[true, Bool]
[42, Int32]
[2.73, Float64]
['Q', Char]
["string", String]
[[1, 2, 3], Array(Int32)] 
[{a: 1, b: 2}, NamedTuple(a: Int32, b: Int32)]
Crystal の整数は Int32、浮動小数点数は Float64 です。
サイズを指定した数リテラル
[1_i64, 2_u32, 3_u64, 4_i32, 5_i16, 6_u8, 7_i128, 8_u128, 3.14_f32, 1.44_f64].each{|x|
  p [x, x.class]
}
ruby
Rubyでは、サーフィックスの付いた数値リテラルは無効
crystalの実行結果
[1, Int64]
[2, UInt32]
[3, UInt64]
[4, Int32]
[5, Int16]
[6, UInt8]
[7, Int128]
[8, UInt128]
[3.14, Float32] 
[1.44, Float64]
Crystal では、数値リテラルに _ で始まるサーフィックスを付け { i:符号付き整数, u:符号なし整数, f:浮動小数点数 } と { 8,16,32,64,128 } のビット幅の組合せです[5]

Setは組込みクラス編集

Crystalでは、Set(集合)は組込みクラスなので、require "set"は不要です。

集合の例
a = Set.new(10.times)
b = Set.new(5.times.map{|i|2*i})
p! a,
	b,
	a + b,
	a - b,
	a & b,
	a | b
	a ^ b
実行結果
a     # => Set{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
b     # => Set{0, 2, 4, 6, 8}
a + b # => Set{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
a - b # => Set{1, 3, 5, 7, 9}
a & b # => Set{0, 2, 4, 6, 8}
a | b # => Set{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

for式がない編集

Crystal には、Ruby にはある for式がありません。

Rubyのfor式の構文
for 変数 in コレクション
  
end
コレクションは Range, Array, Hash など内部構造を持つオブジェクトです。
for式は、最後に評価した値を返すので、forです。
for式のeachメソッドによる置換え
for x in [ 2, 3, 5, 7, 11 ] do
  p x
end
# ↓
[ 2, 3, 5, 7, 11 ].each do | x |
  p x
end
の様にコレクションの each メソッドで置換え可能なので、Rubyからの移植でも小規模な書換えで済みます[6](後述のマクロで実装できないかと思いましたが、いまのところ無理のようです)。

また loop 式もありませんが while true; … end で間に合います。Ruby では while 式の条件の次に do が置けますが、Crystal では置けません。

自作のforメソッド編集

Rubyのforに似せるという縛りがなければ、(マクロを使うまでもなく)簡単に実装できます。 偶然ですが、Scalaのforメソッドに似てしまいました(あれも、イテレーション メソッドに展開されるのである程度は必然)。Scalaと同じ様にジェネレターと組合わせて多次元に拡張することもできそうです。

自作のforメソッド
def for(collection)
  collection.each do |elm|
    yield(elm)
  end
end

for [1,2,3,4] do |x|
  p! x * x
end

for ({3,4,5,6}) do |x|
  p! x + x
end

for (1...999) do |x|
  p! x - 1
  break if x > 5
end

for (9999.times) do |x|
  p! x ** 3
  break if x > 7
end
実行結果
x * x # => 1
x * x # => 4
x * x # => 9
x * x # => 16
x + x # => 6
x + x # => 8
x + x # => 10
x + x # => 12
x - 1 # => 0
x - 1 # => 1
x - 1 # => 2
x - 1 # => 3
x - 1 # => 4
x - 1 # => 5
x ** 3 # => 0
x ** 3 # => 1
x ** 3 # => 8
x ** 3 # => 27
x ** 3 # => 64
x ** 3 # => 125
x ** 3 # => 216
x ** 3 # => 343
x ** 3 # => 512

eval()がない編集

Crystal には eval() はありません。 Crystalはコンパイル型言語ですので、無理もないことです。 もし、Crystal で eval() を実装しようとすると、Common Lisp の様にインタープリターを丸ごとランタイムに含む必要があります。 これはリーズナブルな選択ではありません。 Crystal では、eval() が必要なケースに(限定的ですが)マクロを使うことで実現出来る可能性があります。

マクロ編集

Crystalには、Rubyにはないマクロがあります[7]。Rubyは実行時にすべてのオブジェクトにアクセス出来て、メソッド生やし放題なのでマクロは必要ありませんが、Crystalはコンパイル時に型やメソッドを確定する必要があり、特にメソッドジェネレターとしてのマクロにニーズがあります。また、テンプレート言語的なマクロなので、環境変数による条件分岐や、コンテナを渡し繰返し処理する構文もあります(面白いことにマクロには for 文があり、反対にマクロの中では、eachメソッドは使えません)。マクロには {{attr.id}} の様にASTへのアクセス手順が用意されており、半ば言語を拡張するようなアプローチを取ることも出来ます。

[TODO:ASTについての解説;コラム向き?]

マクロを使ったattr_accessorのイミュレーション
class Point
  def initialize(@x : Int32, @y : Int32)
  end

  # macro定義
  macro attr_accessor(*attrs)
    {% for attr in attrs %}
      def {{attr.id}}() @{{attr.id}} end
      def {{attr.id}}=(var) @{{attr.id}} = var end
    {% end %}
  end

  # macro呼出し
  attr_accessor :x, :y
end

pt = Point.new(20, 30)
p [pt.x, pt.y]
t = pt.x
pt.x = pt.y
pt.y = t
p [pt.x, pt.y]
実行結果
[20, 30]
[30, 20]
Ruby には、attr_accessor と言う「クラスのメンバーのアクセサーを自動生成するメソッド」がありますが、Crystalにはないようなので、マクロで実装しました。
attr_accessor :name からは
def name()     @name end
def name=(val) @name = val end
相当のコードが生成されます。

[TODO:マクロの機能と構文の説明 *の付いた引数、 {{引数}}、{% … %} 構文]

マクロ p!編集

メソッド p は、与えられた「式」の inspaect() の返す値を puts しますが、マクロ p! は、それに先んじて(評価前の)「式」を表示します[8]

p!の例
x, y = true, false

p! x,y,x && y, x || y, x ^ y, !x, x != y, x == y

ary = [ 1, 2, 3 ]

p! ary

p! ary.map(&. << 1)

p! ary.map(&.to_f)
実行結果
x      # => true
y      # => false
x && y # => false
x || y # => true
x ^ y  # => true
!x     # => false
x != y # => true
x == y # => false
ary # => [1, 2, 3]
ary.map(&.<<(1)) # => [2, 4, 6]
ary.map(&.to_f) # => [1.0, 2.0, 3.0]
入れ子のp!編集

マクロ p! は入れ子に出来ます。また、一旦ASTに変換してから再度ソースコードに変換するので、等価な別の構文に変換されることがあります。

入れ子のp!
p! (
  100.times{|i|
    p! i
    break i if i > 12
  }
)
実行結果
(100.times do |i|
 p!(i)
 if i > 12
   break i
 end
end) # => i
# => 0
i # => 1
i # => 2
i # => 3
i # => 4
i # => 5
i # => 6
i # => 7
i # => 8
i # => 9
i # => 10
i # => 11
i # => 12
i # => 13
13

クラス編集

シンプルなクラス編集

シンプルなクラス
class Hello
  def initialize(@name : String = "World")
  end

  def greeting
    puts "Hello #{@name}!"
  end
end

hello = Hello.new()
hello.greeting

universe = Hello.new("Universe")
universe.greeting
実行結果
Hello World!
Hello Universe!
初期化メソッド
  def initialize(@name : String = "World")
  end
Rubyであれば
  def initialize(name = "World")
    @name = name
  end
とするところですが、Crystalでは、型アノテーション  : String を使い、引数の型を限定しました。
また、(@ 付きの)アトリビュート名を仮引数にすると、そのままアトリビュート(a.k.a. インスタンス変数)に仮引数が代入されます。
これは、C++のコンストラクターのメンバー初期化リストと同じアイディアですが、Crystalではインスタンス変数に @ が前置されるので、仮引数に @ が出現すればインスタンス変数の初期値だと自明で、聡明な選択です。


都市間の大圏距離編集

Ruby#ユーザー定義クラスの都市間の大圏距離を求めるメソッドを追加した例を、Crystalに移植しました。

都市間の大圏距離
class GeoCoord
  getter :longitude, :latitude

  def initialize(@longitude : Float64, @latitude : Float64)
  end

  def to_s(io)
    ew, ns = "東経", "北緯"
    long, lat = @longitude, @latitude
    ew, long = "西経", -long if long < 0.0
    ns, lat = "南緯", -lat if lat < 0.0
    io << "(#{ew}: #{long}, #{ns}: #{lat})"
  end # https://github.com/crystal-lang/crystal/issues/259
  def distance(other)
    i, r = Math::PI / 180, 6371.008
    Math.acos(Math.sin(@latitude*i) * Math.sin(other.latitude * i) +
              Math.cos(@latitude*i) * Math.cos(other.latitude * i) * Math.cos(@longitude * i - other.longitude * i)) * r
  end
end

# メソッドの先頭を大文字に出来ないのでクラス名のメソッドは作ることが出来ない
# def GeoCoord(lng : Float64, lat : Float64)
#  GeoCoord.new(lng, lat)
# end

Sites = {
  "東京駅":         GeoCoord.new(139.7673068, 35.6809591),
  "シドニー・オペラハウス": GeoCoord.new(151.215278, -33.856778),
  "グリニッジ天文台":    GeoCoord.new(-0.0014, 51.4778),
}

Sites.each { |name, gc|
  puts "#{name}: #{gc}"
}

puts ""

keys, len = Sites.keys, Sites.size
keys.each_with_index { |x, i|
  y = keys[(i + 1) % len]
  puts "#{x}#{y}: #{Sites[x].distance(Sites[y])} [km]"
}
実行結果
東京駅: (東経: 139.7673068, 北緯: 35.6809591)
シドニー・オペラハウス: (東経: 151.215278, 南緯: 33.856778)
グリニッジ天文台: (西経: 0.0014, 北緯: 51.4778)

東京駅 ⇔ シドニー・オペラハウス: 7823.269299386704 [km]
シドニー・オペラハウス ⇔ グリニッジ天文台: 16987.2708377249 [km]
グリニッジ天文台 ⇔ 東京駅: 9560.546566490015 [km]
Crystal には、attr_accessor はありませんが、標準ライブラリーのマクロに getterがあるので
  getter :longitude, :latitude
としました。
将来、attr_accessor が実装される可能性はありますが、姉妹品のsetter との併用が下位互換性を考えると確実です。
to_s は、Ruby ならば
  def to_s()
    "(#{ew}: #{long}, #{ns}: #{lat})"
ですが、Crystalでは追加の引数 io が必要で
  def to_s(io)
    io << "(#{ew}: #{long}, #{ns}: #{lat})"
Ruby にはクラス名と同じ名前のメソッドで .new を呼出す文化があるのですが、Crystalはメソッドの先頭を大文字に出来ないので、これは見送りました。

包含と継承編集

JavaScript/クラス#包含と継承を、Rubyに移植したRuby#包含と継承を、Crystalに移植しました。

包含と継承の例
class Point
  def initialize(@x = 0, @y = 0)
  end

  def inspect(io)
    io << "x:#{@x}, y:#{@y}"
  end

  def move(dx = 0, dy = 0)
    @x, @y = @x + dx, @y + dy
    self
  end
end

class Shape
  def initialize(x = 0, y = 0)
    @location = Point.new(x, y)
  end

  def inspect(io)
    @location.inspect(io)
  end

  def move(x, y)
    @location.move(x, y)
    self
  end
end

class Rectangle < Shape
  def initialize(x = 0, y = 0, @width = 0, @height = 0)
    super(x, y)
  end

  def inspect(io)
    super(io)
    io << ", width:#{@width}, height:#{@height}"
  end
end

rct = Rectangle.new(12, 32, 100, 50)
p! rct,
  rct.is_a?(Rectangle),
  rct.is_a?(Shape),
  rct.is_a?(Point),
  rct.move(11, 21)
(END)
実行結果
rct                  # => x:12, y:32, width:100, height:50 
rct.is_a?(Rectangle) # => true 
rct.is_a?(Shape)     # => true 
rct.is_a?(Point)     # => false 
rct.move(11, 21)     # => x:23, y:53, width:100, height:50
crystal tool hierarchy
% crystal tool hierarchy inclusion-and-inheritance.cr -e Shape
- class Object (4 bytes)
  |
  +- class Reference (4 bytes)
     |
     +- class Shape (16 bytes)
        .   @location : Point (8 bytes)
        |
        +- class Rectangle (24 bytes)
               @width  : Int32 (4 bytes)
               @height : Int32 (4 bytes)
crystal の tool hierarchy サブコマンドで、クラスの継承関係がわかります。
superclass と subclasses編集

Crystal には、RubyのClassにあるメソッド superclass と subclasses がないので、マクロで実装しました。

superclass と subclasses
class Class
  def self.superclass
    {{ @type.superclass }}
  end

  def self.subclasses : Array(self.class)
    {{ @type.subclasses }}.map(&.as(self.class))
  end

  def self.all_subclasses : Array(self.class)
    {% begin %}
      [{{ @type.all_subclasses.join(",").id }}] of self.class
    {% end %}
  end
end
 
class A end
class AA < A end
class AAA < AA end
class AAB < AA end
class AB < A end

p! A,
  A.subclasses,
  A.all_subclasses,
  AAA.superclass,
  A.superclass
 
c = AAA
while !c.is_a? Nil
  p! c.superclass
  c = c.superclass
end
実行結果
A                # => A 
A.subclasses     # => [AA, AB] 
A.all_subclasses # => [AA, AAA, AAB, AB] 
AAA.superclass   # => AA 
A.superclass     # => Reference 
c.superclass # => AA 
c.superclass # => A 
c.superclass # => Reference 
c.superclass # => Object 
c.superclass # => nil

抽象クラス編集

Java/抽象クラスを、Crystalに移植しました。

抽象クラスの宣言
abstract class クラス名
  #
end
このクラス名は、 .new でインスタンス化出来ません。
Error: can't instantiate abstract class クラス名
となります。
インスタンス化することは出来ませんが、抽象クラスを別のクラスが継承する事は出来ます。
また、抽象クラスを super() を使うことでメソッドを呼び出せるので、抽象メソッドではないメソッド(具象メソッド)を持つことも、インスタンス変数も持つことも出来ます。
抽象クラスの例では、Shapeのinitializeメソッドが抽象クラスの具象メソッドとなっています。
抽象メソッドの宣言
  abstract def メソッド名
派生先のクラスで、「メソッド名」を定義(def)し忘れると
Error: abstract `def クラス名#メソッド名()` must be implemented by クラス名
となります
抽象クラスの例
abstract class Shape
  def initialize(@x = 0.0, @y = 0.0)
  end

  abstract def to_s(io)
  abstract def area
end

class Square < Shape
  def initialize(x, y, @wh = 0.0)
    super(x, y)
  end

  def to_s(io)
    io << "Square(#{@x}, #{@y}, #{@wh})"
  end

  def area
    @wh * @wh
  end
end

abstract class Shape
  def initialize(@x = 0.0, @y = 0.0)
  end

  abstract def to_s(io)
  abstract def area
end

class Square < Shape
  def initialize(x, y, @wh = 0.0)
    super(x, y)
  end

  def to_s(io)
    io << "Square(#{@x}, #{@y}, #{@wh})"
  end

  def area
    @wh * @wh
  end
end

class Recrangle < Shape
  def initialize(x, y, @w = 0.0, @h = 0.0)
    super(x, y)
  end

  def to_s(io)
    io << "Rectanle(#{@x}, #{@y}, #{@w}, #{@h})"
  end

  def area
    @w * @h
  end
end

class Circle < Shape
  def initialize(x, y, @r = 0.0)
    super(x, y)
  end

  def to_s(io)
    io << "Circle(#{@x}, #{@y}, #{@r})"
  end

  def area
    3.1425926536 * @r * @r
  end
end

shapes = [
  Square.new(5.0, 10.0, 15.0),
  Recrangle.new(13.0, 23.0, 20.0, 10.0),
  Circle.new(3.0, 2.0, 20.0),
] of Shape

shapes.each do |shape|
  puts("#{shape}: #{shape.area}")
end
実行結果
Square(5.0, 10.0, 15.0): 225.0
Rectanle(13.0, 23.0, 20.0, 10.0): 200.0
Circle(3.0, 2.0, 20.0): 1257.03706144
crystal tool hierarchy
% crystal tool hierarchy abstract.cr -e Shape
- class Object (4 bytes)
  |
  +- class Reference (4 bytes)
     |
     +- class Shape (24 bytes)
        .   @x : Float64 (8 bytes)
        .   @y : Float64 (8 bytes)
        |
        +- class Circle (32 bytes)
        |      @r : Float64 (8 bytes)
        |
        +- class Recrangle (40 bytes)
        |      @w : Float64 (8 bytes)
        |      @h : Float64 (8 bytes)
        |
        +- class Square (32 bytes)
               @wh : Float64 (8 bytes)
crystal の tool hierarchy サブコマンドで、クラスの継承関係がわかります。
「包含と継承の例」と比べると、ShapeとRectangleが同じ階層にあることがわかると思います。

[TODO:virtual class::いい例がない]

キーワード編集

Crystalのキーワード( keywords ) は、以下の通り。

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

演算子編集

Crystalは、1つ、2つ、または3つのオペランドを持つ数多くの演算子をサポートしています[9]

演算子式は、実際にはメソッド呼び出しとしてパースされます。例えば、a + ba.+(b) と意味的に同じで、引数 b を持つ a のメソッド + を呼び出すことになります。

演算子の優先度
種類 演算子
インデックス アクセサー [], []?
単項 +, &+, -, &-, !, ~
指数 **, &**
乗除 *, &*, /, //, %
加減 +, &+, -, &-
シフト <<, >>
ビット間 AND &
ビット間 OR/XOR |,^
等値 ==, !=, =~, !~, ===
比較 <, <=, >, >=, <=>
論理 AND &&
論理 OR ||
Range .., ...
条件 ?:
代入 =, []=, +=, &+=, -=, &-=, *=, &*=, /=, //=, %=, |=, &=,^=,**=,<<=,>>=, ||=, &&=
スプラット *, **

制御構造編集

制御構造(せいぎょこうぞう、control flow)とは、「順次」「分岐」「反復」という基本的な処理のことを言います。

Crystalの真理値
制御構造は「条件式」が真であるか偽であるかによって分岐や反復の振る舞いが変わります。

では「条件式」が真・偽はどの様に決まるのでしょう?

Crystalでは false あるいは nil であると偽、それ以外が真です。

なので 0[](空のArray) も {}(空のNamedTuple)も真です。

条件分岐編集

Crystalの条件分岐には、if, untilcaseの3つの構文があります。

if編集

ifは条件式によって実行・否を切り替える構造構文で、評価した式の値を返すので条件演算子でもあります。

ifの例
a = 0.0 / 0.0

if a < 0
    puts "minus"
elsif a > 0
    puts "plus"
elsif a == 0
    puts "zero"
else
    puts a
end

p! (
    if a < 0
        "minus"
    elsif a > 0
        "plus"
    elsif a == 0
        "zero"
    else
        a
    end
)
実行結果
NaN 
(if a < 0 
 "minus" 
else 
 if a > 0 
   "plus" 
 else 
   if a == 0 
     "zero" 
   else 
     a 
   end 
 end 
end) # => NaN
elsif節
ifは、オプショナルな elsif 節を設け、条件式が偽であった時に別の条件に合致した処理を実行させることが出来ます。
else節
ifは、オプショナルな else 節を設け、条件式が偽であった時に処理を実行させることが出来ます。
ifは値を返すので、メソッドの実引数に使うことが出来ますし、代入演算の右辺にも使えます。

後置のif編集

Crystalには、RubyやPerlのような後置のifがあります。

後置のifの例
n = 0

puts "nは0" if n == 0
puts "nは1" if n == 1
実行結果
nは0

unless編集

unless(アンレス)は条件式によって実行・否を切り替える構造構文ですが、ifとは条件式に対する挙動が逆です。

unless文の例
a = 0.0 / 0.0

unless a == 0
    puts "Non-zero"
else
    puts a
end
実行結果
Non-zero
else節
unless文は、オプショナルな else 節を設け、条件式が真であった時に処理を実行させることが出来ます。
また、unless文は elsif 節は持てません。

後置のunless編集

Crystalには、RubyやPerlのような後置のunlessがあります。

後置のunlessの例
n = 0

puts "nは0" unless n == 0
puts "nは1" unless n == 1
実行結果
nは1ではない

case編集

caseは、複数の条件式によって処理を降る分ける用途の為に用意されています。

caseの例
n = 2

case n
when 1
    puts "one"
when 2
    puts "two"
when 3
    puts "three"
else
    puts "other"
end

p! (
case n
when 1
    "one"
when 2
    "two"
when 3
    "three"
else
    "other"
end
)
実行結果
two 
(case n 
when 1 
 "one" 
when 2 
 "two" 
when 3 
 "three" 
else 
 "other" 
end) # => "two"
C言語系のswitch文に慣れた人はbreakがないことに気がつくと思います。Crystalのcaseはfall throughしませんし、fall throughさせる方法もありません。
when節が定数でなく式を受付けます編集

ifを使ったコードをcaseに書き換えてみましょう。

case の式の省略
a = 0.0 / 0.0
 
case
when a < 0
  puts "minus"
when a > 0
  puts "plus"
when a == 0
  puts "zero"
else
  puts a
end
 
p! (
case true
when a < 0
  "minus"
when a > 0
  "plus"
when a == 0
  "zero"
else
  a
end
)
実行結果
NaN 
(case true 
when a < 0 
 "minus" 
when a > 0 
 "plus" 
when a == 0 
 "zero" 
else 
 a 
end) # => NaN

このコードは when 節の式の値とcaseの式を === で比較し、最初に一致した when に対応する式が実行される事を利用しています。

型による分岐編集

when 節が式ではなく型であった場合、caseの式を is_a? で評価し、最初に一致した when に対応する式が実行されます。

型による分岐
p! 0.class,
	0.is_a?(Object),
	0.is_a?(Int32),
	0.is_a?(Number),
	0.is_a?(String)
 
case 0
when String
    puts "String"
when Number
    puts "Number"
when Int32
    puts "Int32"
when Object
    puts "Object"
else
    puts "Unknown"
end
実行結果
0.class         # => Int32 
0.is_a?(Object) # => true 
0.is_a?(Int32)  # => true 
0.is_a?(Number) # => true 
0.is_a?(String) # => false 
Number

暗黙のオブジェクト構文を使うと

case 0
when .is_a?(String)
    puts "String"
when .is_a?(Number)
    puts "Number"
when .is_a(Int32)
    puts "Int32"
when .is_a(Object)
    puts "Object"
else
    puts "Unknown"
end
と書くことが出来ます。
メソッドは、.is_a? に限定しないので、 .odd? .even? .include? など Bool を返すメソッドなら何でも使えます。

when に対応する式は、1つのことが珍しくないので、その場合は省略可能な then を補うと、1行で書けます。

case 0
when String then puts "String"
when Number then puts "Number"
when Int32  then puts "Int32"
when Object then puts "Object"
else             puts "Unknown"
end

[TODO:タプルとダミー識別子 _ ]

網羅性の検査編集

when の代わりに in を使用すると、exhaustive case 式が作成されます。exhaustive case では、必要な in 条件を省略するとコンパイル時にエラーとなります。exhaustive case 式では、when 節と else 節を含むことはできません。

Enumの網羅性チェック(網羅不完全)
enum Colours
  Red
  Green
  Blue
end

colour : Colours = Colours::Red
q = case colour
    in Colours::Red then "赤"
    in .green?      then "緑"
#    in .blue?       then "青"
    end

p q
コンパイルエラー
Showing last frame. Use --error-trace for full trace.

In enumcase.cr:8:5

 8 | q = case colour
         ^
Error: case is not exhaustive for enum Colours.

Missing members:
 - Blue
case - in 式の in が列挙型の要素を網羅していないと、コンパイル時にこの様にエラーになります。
Colours::Red と .red? は同義です(enum では、要素名を小文字にし最後に ? が付いたメソッドが生えてきます)。
Enumの網羅性チェック(網羅完全)
enum Colours
  Red
  Green
  Blue
end

colour : Colours = Colours::Red
q = case colour
    in Colours::Red then "赤"
    in .green?      then "緑"
    in .blue?       then "青"
    end

p q
実行結果
"赤"

[TODO:短絡評価 && || ]

繰返し編集

Crystalには、他のプログラミング言語のような繰返し構文と、イテレーターメソッドがあります。

繰返し構文編集

Crystalの繰返し構文には、while と untilの2つがあります[10]

while編集

while(ホワイル)は条件がである間、式を実行しつづけます。

構文
while 条件式
    式1
    式2
      
    式n
end
Rubyと違い、条件式の後ろに do をつけることは出来ません。
while文のコード例
i = 0
p! (
while i < 10
    p! i
    i += 1
    break i if i > 5
end
)
2行目の i < 5が真の間、次の2行を繰返します。
4行目の i += 1i = i + 1 の構文糖
実行結果
(while i < 10 
 p!(i) 
 i = i + 1 
 if i > 5 
   break i 
  end 
end)# => 
i # => 0 
i # => 1 
i # => 2 
i # => 3 
i # => 4 
i # => 5 
6
until編集

until(アンティル)は条件がである間、式を実行しつづけます。whileとは条件に対する挙動が逆です。

構文
until 条件式 [ do ]
    文1
    文2
      
    文n
end
do は省略できます。
untilのコード例
i = 0
until i == 3
    puts i
    i += 1
end
2行目の i == 3が偽の間、次の2行を繰返します。
実行結果
0
1
2
for編集

Crystalにはforがありませんが、コレクションのイテレーションメソッドを使うことで繰返しを簡素に実現出来ます。

Rangeオブジェクト編集

Rangeオブジェクトは、整数の区間を表し範囲演算子 開始 .. 終了開始 ... 終了 で生成します。 範囲演算子の終了は省略でき、その場合は数学の半開区間(半閉区間)となり、例えば、1 ..は自然数となります(ただし、日本的な0を自然数に含まない場合)。

コード
rng = 1..3
puts rng.class
rng.each do | n |
  puts "#{n}番";
end
実行結果
Range(Int32, Int32) 
1番 
2番 
3番

Arrayオブジェクト編集

Arrayオブジェクトは、任意の Crystal オブジェクトを要素として持つことができます。 配列式[ 要素1, 要素2, … 要素n ] で生成します。

コード
animals = [ "ネコ", "金魚", "ハムスター" ]

puts animals.class
animals.each do | animal |
  puts "動物 #{animal}"
end

p! ([ "イヌ", *animals , "イグアナ" ])
実行結果
Array(String)
動物 ネコ
動物 金魚
動物 ハムスター
(["イヌ", *animals, "イグアナ"]) # => ["イヌ", "ネコ", "金魚", "ハムスター", "イグアナ"]

Tupleオブジェクト編集

Tupleオブジェクトは、任意の Crystal オブジェクトを要素として持つことができます。 配列式{ 要素1, 要素2, … 要素n } で生成します。

コード
animals = { "ネコ", "金魚", "ハムスター" }

puts animals.class
animals.each do | animal |
  puts "動物 #{animal}"
end

p! ({ "イヌ", *animals , "イグアナ" })
実行結果
Tuple(String, String, String)
動物 ネコ
動物 金魚
動物 ハムスター
({"イヌ", *animals, "イグアナ"}) # => {"イヌ", "ネコ", "金魚", "ハムスター", "イグアナ"}

Setオブジェクト編集

Setオブジェクトは集合です。任意の Crystal オブジェクトを要素として持つことができますが、1つの値は重複して持てません。 Set.newに配列式{ 要素1, 要素2, … 要素n } などを渡し初期化します。

コード
animals = Set.new({ "ネコ", "金魚", "ハムスター" })
 
puts animals.class
animals.each do | animal |
  puts "動物 #{animal}"
end
 
p! animals,
  animals.includes?("ネコ"),
  animals.includes?("イヌ")
 
animals.delete "ネコ"
animals.add    "イヌ"
 
p! animals,
  animals.includes?("ネコ"),
  animals.includes?("イヌ")
 
animals = Set.new({ "ネコ", "イヌ", "金魚", "ハムスター", "カナリヤ", "クサガメ" })
mammals = Set.new({ "ネコ", "イヌ", "ハムスター" })
p!  animals , mammals,
   animals & mammals,
   animals | mammals,
   animals + mammals,
   animals ^ mammals,
   animals - mammals,
   mammals - animals
実行結果
Set(String)
動物 ネコ
動物 金魚
動物 ハムスター
animals                 # => Set{"ネコ", "金魚", "ハムスター"}
animals.includes?("ネコ") # => true
animals.includes?("イヌ") # => false
animals                 # => Set{"金魚", "ハムスター", "イヌ"}
animals.includes?("ネコ") # => false
animals.includes?("イヌ") # => true
animals           # => Set{"ネコ", "イヌ", "金魚", "ハムスター", "カナリヤ", "クサガメ"}
mammals           # => Set{"ネコ", "イヌ", "ハムスター"}
animals & mammals # => Set{"ネコ", "イヌ", "ハムスター"}
animals | mammals # => Set{"ネコ", "イヌ", "金魚", "ハムスター", "カナリヤ", "クサガメ"}
animals + mammals # => Set{"ネコ", "イヌ", "金魚", "ハムスター", "カナリヤ", "クサガメ"}
animals ^ mammals # => Set{"金魚", "カナリヤ", "クサガメ"}
animals - mammals # => Set{"金魚", "カナリヤ", "クサガメ"}
mammals - animals # => Set{}

NamedTupleオブジェクト編集

NamedTupleオブジェクトは、任意の Crystal オブジェクトをキーに、任意の Crystal オブジェクトを値に持つことができる連想配列です。 NamedTuple式{キー1 => 値1, キー2 => 値2, キーn => 値n} で生成します。 また、キーが Symbol の場合 NamedTuple式{キー1: 値1, キー2: 値2, キーn: 値n} で生成することが出来ます。

コード
animals = {cat: "ネコ", gold_fish: "金魚", hamster: "ハムスター"}
 
puts animals.class
 
animals.each do | en, animal |
  puts "動物 #{en}: #{animal}"
end
実行結果
NamedTuple(cat: String, gold_fish: String, hamster: String)
動物 cat: ネコ
動物 gold_fish: 金魚
動物 hamster: ハムスター

このように、Crystalではforがなくてもコレクションのメソッドで同様の処理を実現できます。

loop編集

loop ありません。 while true で代用します。

loopの代用コード例
i = 1
while true
    puts "0b%b" % i
    i <<= 1
    break if i > 2**8
end
実行結果
0b1
0b10
0b100
0b1000
0b10000
0b100000
0b1000000
0b10000000 
0b100000000
5行目の、break if i > 2**8でループを脱出するようにしています。この様に break や return あるいは例外が上がらないとループは永久に終わりません。
このコードは、Crystalにはない do-while文を模倣する例にもなっています。

イテレーターメソッド編集

Integer#times編集

Integer#timesは与えられたブロックをオブジェクトの示す整数値回くりかえします。

コード
3.times{ puts "Hello, world!" }
実行結果
Hello, world!
Hello, world!
Hello, world!
ループ変数を使た例
3.times do |i|
  puts "#{i}の倍は#{2 * i}"
end
実行結果
0の倍は0
1の倍は2
2の倍は4
ブロックを伴わないtimesメソッド
iter = 3.times
puts iter.class
p! iter.next
p! iter.next
p! iter.next
p! iter.next
p! iter.next
p! iter.next
# puts iter.next #  `next': StopIteration: iteration reached an end
実行結果
Int::TimesIterator(Int32) 
iter.next # => 0 
iter.next # => 1 
iter.next # => 2 
iter.next # => #<Iterator::Stop:0x7fb5bedd7fe0> 
iter.next # => #<Iterator::Stop:0x7fb5bedd7fe0> 
iter.next # => #<Iterator::Stop:0x7fb5bedd7fe0>
Integer#times にブロックを渡さないと、Int::TimesIterator([T])オブジェクトが返ります。
Int::TimesIterator([T])オブジェクトは外部イテレーターと呼ばれnextメソッドで反復を行えます。

オブジェクト編集

Crystal では、全てがオブジェクトです。

オブジェクトのリテラルとクラス編集

オブジェクトのリテラルとクラス
[nil, false, true, 1, 3.14, "abc", :abc, 1..10, 1...10, 1..,
 [1, 2_u8, 3_i128],
 [1, 2, 3], [1, "abc"],
 {1, 2, 3}, {1, "abc"},
 {"a" => 1, "b" => 2},
 {a: 1, b: 2},
 Set.new([:a, :bc, :def]),
 ->(x : Int32) { 2 * x },
 100.times,
 (1..).each,
 [1, 2, 3].each,
 {1, 2, 3}.each,
 {"a" => 1, "b" => 2}.each,
#  {a:1, b:2}.each, # Error: 'NamedTuple(a: Int32, b: Int32)#each' is expected to be invoked with a block, but no block was given
].each do |obj|
  p [obj, obj.class]
end
実行結果
[nil, Nil]
[false, Bool]
[true, Bool]
[1, Int32]
[3.14, Float64]
["abc", String]
[:abc, Symbol]
[1..10, Range(Int32, Int32)]
[1...10, Range(Int32, Int32)]
[1.., Range(Int32, Nil)]
[[1, 2, 3], Array(Int128 | Int32 | UInt8)]
[[1, 2, 3], Array(Int32)]
[[1, "abc"], Array(Int32 | String)]
[{1, 2, 3}, Tuple(Int32, Int32, Int32)]
[{1, "abc"}, Tuple(Int32, String)]
[{"a" => 1, "b" => 2}, Hash(String, Int32)]
[{a: 1, b: 2}, NamedTuple(a: Int32, b: Int32)]
[Set{:a, :bc, :def}, Set(Symbol)]
[#<Proc(Int32, Int32):0x5a6c0ffabcf0>, Proc(Int32, Int32)]
[#<Int::TimesIterator(Int32):0x7e2b4be59e80 @n=100, @index=0>, Int::TimesIterator(Int32)]
[#<Range::ItemIterator(Int32, Nil):0x7e2b4be5dfc0 @range=1.., @current=1, @reached_end=false>, Range::ItemIterator(Int32, Nil)]
[#<Indexable::ItemIterator(Array(Int32), Int32):0x7e2b4be58da0 @array=[1, 2, 3], @index=0>, Indexable::ItemIterator(Array(Int32), Int32)]
[#<Indexable::ItemIterator(Tuple(Int32, Int32, Int32), Int32):0x7e2b4be5dfa0 @array={1, 2, 3}, @index=0>, Indexable::ItemIterator(Tuple(Int32, Int32, Int32), Int32)]
[#<Hash::EntryIterator(String, Int32):0x7e2b4be58d80 @hash={"a" => 1, "b" => 2}, @index=0>, Hash::EntryIterator(String, Int32)]

Rubyのオブジェクトのリテラルとクラス編集

Rubyのオブジェクトのリテラルとクラス
require 'set'

[nil, false, true, 1, 3.14, "abc", :abc, 1..10, 1...10, 1..,
# [1, 2_u8, 3_i128],
 [1, 2, 3], [1, "abc"],
# {1, 2, 3}, {1, "abc"},
 {"a" => 1, "b" => 2},
 {a: 1, b: 2},
 Set.new([:a, :bc, :def]),
 ->(x) { 2 * x },
 100.times,
 (1..).each,
 [1, 2, 3].each,
# {1, 2, 3}.each,
 {"a" => 1, "b" => 2}.each,
#  {a:1, b:2}.each, # Error: 'NamedTuple(a: Int32, b: Int32)#each' is expected to be invoked with a block, but no block was given
].each do |obj|
  p [obj, obj.class]
end
実行結果
[nil, NilClass]
[false, FalseClass]
[true, TrueClass]
[1, Integer]
[3.14, Float]
["abc", String]
[:abc, Symbol]
[1..10, Range]
[1...10, Range]
[1.., Range]
[[1, 2, 3], Array]
[[1, "abc"], Array]
[{"a"=>1, "b"=>2}, Hash]
[{:a=>1, :b=>2}, Hash]
[#<Set: {:a, :bc, :def}>, Set]
[#<Proc:0x000014af26147eb0 Main.rb:10 (lambda)>, Proc]
[#<Enumerator: 100:times>, Enumerator]
[#<Enumerator: 1..:each>, Enumerator]
[#<Enumerator: [1, 2, 3]:each>, Enumerator]
[#<Enumerator: {"a"=>1, "b"=>2}:each>, Enumerator]

メソッド編集

オブジェクトの値や機能を呼び出すためには、メソッドを使います(多くの演算子もメソッドです)。

クラスのメソッド一覧編集

Crystal には、Objectクラスにmethodsメソッドがないので、マクロで実装しました。

RubyのObject#methods
p Object.methods.sort,
  Integer.methods.sort,
  Float.methods.sort,
  Array.methods.sort,
  Range.methods.sort
実行結果
[:!, :!=, :!~, :<, :<=, :<=>, :==, :===, :=~, :>, :>=, :__id__, :__send__, :alias_method, :allocate, :ancestors, :attr, :attr_accessor, :attr_reader, :attr_writer, :autoload, :autoload?, :class, :class_eval, :class_exec, :class_variable_defined?, :class_variable_get, :class_variable_set, :class_variables, :clone, :const_defined?, :const_get, :const_missing, :const_set, :const_source_location, :constants, :define_method, :define_singleton_method, :deprecate_constant, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :include, :include?, :included_modules, :inspect, :instance_eval, :instance_exec, :instance_method, :instance_methods, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :itself, :kind_of?, :method, :method_defined?, :methods, :module_eval, :module_exec, :name, :new, :nil?, :object_id, :prepend, :private_class_method, :private_constant, :private_instance_methods, :private_method_defined?, :private_methods, :protected_instance_methods, :protected_method_defined?, :protected_methods, :public_class_method, :public_constant, :public_instance_method, :public_instance_methods, :public_method, :public_method_defined?, :public_methods, :public_send, :remove_class_variable, :remove_instance_variable, :remove_method, :respond_to?, :send, :singleton_class, :singleton_class?, :singleton_method, :singleton_methods, :subclasses, :superclass, :taint, :tainted?, :tap, :then, :to_enum, :to_s, :trust, :undef_method, :untaint, :untrust, :untrusted?, :yield_self]
[:!, :!=, :!~, :<, :<=, :<=>, :==, :===, :=~, :>, :>=, :__id__, :__send__, :alias_method, :allocate, :ancestors, :attr, :attr_accessor, :attr_reader, :attr_writer, :autoload, :autoload?, :class, :class_eval, :class_exec, :class_variable_defined?, :class_variable_get, :class_variable_set, :class_variables, :clone, :const_defined?, :const_get, :const_missing, :const_set, :const_source_location, :constants, :define_method, :define_singleton_method, :deprecate_constant, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :include, :include?, :included_modules, :inspect, :instance_eval, :instance_exec, :instance_method, :instance_methods, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :itself, :kind_of?, :method, :method_defined?, :methods, :module_eval, :module_exec, :name, :nil?, :object_id, :prepend, :private_class_method, :private_constant, :private_instance_methods, :private_method_defined?, :private_methods, :protected_instance_methods, :protected_method_defined?, :protected_methods, :public_class_method, :public_constant, :public_instance_method, :public_instance_methods, :public_method, :public_method_defined?, :public_methods, :public_send, :remove_class_variable, :remove_instance_variable, :remove_method, :respond_to?, :send, :singleton_class, :singleton_class?, :singleton_method, :singleton_methods, :sqrt, :subclasses, :superclass, :taint, :tainted?, :tap, :then, :to_enum, :to_s, :trust, :try_convert, :undef_method, :untaint, :untrust, :untrusted?, :yield_self]
[:!, :!=, :!~, :<, :<=, :<=>, :==, :===, :=~, :>, :>=, :__id__, :__send__, :alias_method, :allocate, :ancestors, :attr, :attr_accessor, :attr_reader, :attr_writer, :autoload, :autoload?, :class, :class_eval, :class_exec, :class_variable_defined?, :class_variable_get, :class_variable_set, :class_variables, :clone, :const_defined?, :const_get, :const_missing, :const_set, :const_source_location, :constants, :define_method, :define_singleton_method, :deprecate_constant, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :include, :include?, :included_modules, :inspect, :instance_eval, :instance_exec, :instance_method, :instance_methods, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :itself, :kind_of?, :method, :method_defined?, :methods, :module_eval, :module_exec, :name, :nil?, :object_id, :prepend, :private_class_method, :private_constant, :private_instance_methods, :private_method_defined?, :private_methods, :protected_instance_methods, :protected_method_defined?, :protected_methods, :public_class_method, :public_constant, :public_instance_method, :public_instance_methods, :public_method, :public_method_defined?, :public_methods, :public_send, :remove_class_variable, :remove_instance_variable, :remove_method, :respond_to?, :send, :singleton_class, :singleton_class?, :singleton_method, :singleton_methods, :subclasses, :superclass, :taint, :tainted?, :tap, :then, :to_enum, :to_s, :trust, :undef_method, :untaint, :untrust, :untrusted?, :yield_self]
[:!, :!=, :!~, :<, :<=, :<=>, :==, :===, :=~, :>, :>=, :[], :__id__, :__send__, :alias_method, :allocate, :ancestors, :attr, :attr_accessor, :attr_reader, :attr_writer, :autoload, :autoload?, :class, :class_eval, :class_exec, :class_variable_defined?, :class_variable_get, :class_variable_set, :class_variables, :clone, :const_defined?, :const_get, :const_missing, :const_set, :const_source_location, :constants, :define_method, :define_singleton_method, :deprecate_constant, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :include, :include?, :included_modules, :inspect, :instance_eval, :instance_exec, :instance_method, :instance_methods, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :itself, :kind_of?, :method, :method_defined?, :methods, :module_eval, :module_exec, :name, :new, :nil?, :object_id, :prepend, :private_class_method, :private_constant, :private_instance_methods, :private_method_defined?, :private_methods, :protected_instance_methods, :protected_method_defined?, :protected_methods, :public_class_method, :public_constant, :public_instance_method, :public_instance_methods, :public_method, :public_method_defined?, :public_methods, :public_send, :remove_class_variable, :remove_instance_variable, :remove_method, :respond_to?, :send, :singleton_class, :singleton_class?, :singleton_method, :singleton_methods, :subclasses, :superclass, :taint, :tainted?, :tap, :then, :to_enum, :to_s, :trust, :try_convert, :undef_method, :untaint, :untrust, :untrusted?, :yield_self] 
[:!, :!=, :!~, :<, :<=, :<=>, :==, :===, :=~, :>, :>=, :__id__, :__send__, :alias_method, :allocate, :ancestors, :attr, :attr_accessor, :attr_reader, :attr_writer, :autoload, :autoload?, :class, :class_eval, :class_exec, :class_variable_defined?, :class_variable_get, :class_variable_set, :class_variables, :clone, :const_defined?, :const_get, :const_missing, :const_set, :const_source_location, :constants, :define_method, :define_singleton_method, :deprecate_constant, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :include, :include?, :included_modules, :inspect, :instance_eval, :instance_exec, :instance_method, :instance_methods, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :itself, :kind_of?, :method, :method_defined?, :methods, :module_eval, :module_exec, :name, :new, :nil?, :object_id, :prepend, :private_class_method, :private_constant, :private_instance_methods, :private_method_defined?, :private_methods, :protected_instance_methods, :protected_method_defined?, :protected_methods, :public_class_method, :public_constant, :public_instance_method, :public_instance_methods, :public_method, :public_method_defined?, :public_methods, :public_send, :remove_class_variable, :remove_instance_variable, :remove_method, :respond_to?, :send, :singleton_class, :singleton_class?, :singleton_method, :singleton_methods, :subclasses, :superclass, :taint, :tainted?, :tap, :then, :to_enum, :to_s, :trust, :undef_method, :untaint, :untrust, :untrusted?, :yield_self]


Crystalに実装したmethodsマクロ
class Object
  macro methods
    {{ @type.methods.map(&.name.stringify).sort.uniq }}
  end
end

p! Object.methods,
  Reference.methods,
  Array.methods,
  Box.methods,
  Channel.methods,
  Deque.methods,
  Dir.methods,
  Exception.methods,
  ArgumentError.methods,
  DivisionByZeroError.methods,
  IndexError.methods,
  InvalidByteSequenceError.methods,
  Fiber.methods,
  Hash.methods,
  IO.methods,
  File.methods,
  Mutex.methods,
  PrettyPrint.methods,
  Process.methods,
  Regex.methods,
  String.methods,
  Thread.methods,
  Bool.methods,
  Int32.methods,
  Float64.methods,
  Proc.methods
実行結果
Object.methods                   # => ["!=", "!~", "==", "===", "=~", "class", "crystal_type_id", "dup", "hash", "in?", "inspect", "itself", "not_nil!", "pretty_inspect", "pretty_print", "tap", "to_s", "try", "unsafe_as"]
Reference.methods                # => ["==", "dup", "exec_recursive", "exec_recursive_clone", "hash", "inspect", "object_id", "pretty_print", "same?", "to_s"]
Array.methods                    # => ["&", "*", "+", "-", "<<", "<=>", "==", "[]", "[]=", "[]?", "calculate_new_capacity", "check_needs_resize", "check_needs_resize_for_unshift", "clear", "clone", "compact", "compact!", "concat", "delete", "delete_at", "dup", "each_repeated_permutation", "fill", "first", "flatten", "increase_capacity", "increase_capacity_for_unshift", "index", "initialize", "insert", "inspect", "internal_delete", "last", "map", "map_with_index", "needs_resize?", "pop", "pop?", "pretty_print", "product", "push", "reject!", "remaining_capacity", "repeated_permutations", "replace", "reset_buffer_to_root_buffer", "resize_if_cant_insert", "resize_to_capacity", "resize_to_capacity_for_unshift", "reverse", "root_buffer", "rotate", "rotate!", "select!", "shift", "shift?", "shift_buffer_by", "shift_when_not_empty", "shuffle", "size", "size=", "skip", "sort", "sort!", "sort_by", "sort_by!", "to_a", "to_lookup_hash", "to_s", "to_unsafe", "to_unsafe_slice", "transpose", "truncate", "uniq", "uniq!", "unsafe_fetch", "unsafe_put", "unshift", "unstable_sort", "unstable_sort!", "unstable_sort_by", "unstable_sort_by!", "|"]
Box.methods                      # => ["initialize", "object"]
Channel.methods                  # => ["close", "closed?", "dequeue_receiver", "dequeue_sender", "initialize", "inspect", "pretty_print", "receive", "receive?", "receive_impl", "receive_internal", "receive_select_action", "receive_select_action?", "send", "send_internal", "send_select_action"]
Deque.methods                    # => ["+", "<<", "==", "buffer", "clear", "clone", "concat", "delete", "delete_at", "dup", "each", "halfs", "increase_capacity", "initialize", "insert", "inspect", "internal_delete", "pop", "pop?", "pretty_print", "push", "reject!", "rotate!", "select!", "shift", "shift?", "size", "size=", "to_s", "unsafe_fetch", "unsafe_put", "unshift"]
Dir.methods                      # => ["children", "close", "each", "each_child", "entries", "initialize", "inspect", "path", "pretty_print", "read", "rewind", "to_s"]
Exception.methods                # => ["backtrace", "backtrace?", "callstack", "callstack=", "cause", "initialize", "inspect", "inspect_with_backtrace", "message", "to_s"]
ArgumentError.methods            # => ["initialize"]
DivisionByZeroError.methods      # => ["initialize"]
IndexError.methods               # => ["initialize"]
InvalidByteSequenceError.methods # => ["initialize"]
Fiber.methods                    # => ["cancel_timeout", "dead?", "enqueue", "initialize", "inspect", "makecontext", "name", "name=", "next", "next=", "previous", "previous=", "push_gc_roots", "resumable?", "resume", "resume_event", "run", "running?", "stack_bottom", "stack_bottom=", "timeout", "timeout_event", "timeout_select_action", "timeout_select_action=", "to_s"]
Hash.methods                     # => ["==", "[]", "[]=", "[]?", "add_entry_and_increment_size", "clear", "clear_entries", "clear_impl", "clear_indices", "clone", "compact", "compact!", "compare_by_identity", "compare_by_identity?", "compute_indices_bytesize", "delete", "delete_entry", "delete_entry_and_update_counts", "delete_impl", "delete_linear_scan", "dig", "dig?", "do_compaction", "double_indices_size", "dup", "each", "each_entry_with_index", "each_key", "each_value", "empty?", "entries", "entries_capacity", "entries_full?", "entries_size", "entry_matches?", "fetch", "find_entry", "find_entry_with_index", "find_entry_with_index_linear_scan", "first_entry?", "first_key", "first_key?", "first_value", "first_value?", "fit_in_indices", "get_entry", "get_index", "has_key?", "has_value?", "hash", "indices_malloc_size", "indices_size", "initialize", "initialize_clone", "initialize_clone_entries", "initialize_compare_by_identity", "initialize_copy_non_entries_vars", "initialize_default_block", "initialize_dup", "initialize_dup_entries", "inspect", "invert", "key_for", "key_for?", "key_hash", "keys", "last_entry?", "last_key", "last_key?", "last_value", "last_value?", "malloc_entries", "malloc_indices", "merge", "merge!", "merge_into!", "next_index", "pretty_print", "proper_subset_of?", "proper_superset_of?", "put", "realloc_entries", "realloc_indices", "rehash", "reject", "reject!", "resize", "select", "select!", "set_entry", "set_index", "shift", "shift?", "size", "subset_of?", "superset_of?", "to_a", "to_a_impl", "to_h", "to_s", "transform_keys", "transform_values", "transform_values!", "update", "update_linear_scan", "upsert", "values", "values_at"]
IO.methods                       # => ["<<", "check_open", "close", "closed?", "decoder", "each_byte", "each_char", "each_line", "encoder", "encoding", "flush", "getb_to_end", "gets", "gets_peek", "gets_slow", "gets_to_end", "has_non_utf8_encoding?", "peek", "peek_or_read_utf8", "peek_or_read_utf8_masked", "pos", "pos=", "print", "printf", "puts", "read", "read_at", "read_byte", "read_bytes", "read_char", "read_char_with_bytesize", "read_fully", "read_fully?", "read_line", "read_string", "read_utf8", "read_utf8_byte", "rewind", "seek", "set_encoding", "skip", "skip_to_end", "tell", "tty?", "utf8_encoding?", "write", "write_byte", "write_bytes", "write_string", "write_utf8"]
File.methods                     # => ["delete", "initialize", "inspect", "path", "read_at", "size", "truncate"]
Mutex.methods                    # => ["initialize", "lock", "lock_slow", "synchronize", "try_lock", "unlock"]
PrettyPrint.methods              # => ["break_outmost_groups", "breakable", "comma", "current_group", "fill_breakable", "flush", "group", "group_queue", "group_sub", "indent", "initialize", "list", "nest", "newline", "surround", "text"]
Process.methods                  # => ["channel", "close", "close_io", "copy_io", "ensure_channel", "error", "error?", "exists?", "finalize", "initialize", "input", "input?", "output", "output?", "pid", "signal", "stdio_to_fd", "terminate", "terminated?", "wait"]
Regex.methods                    # => ["+", "==", "===", "=~", "capture_count", "clone", "dup", "finalize", "hash", "initialize", "inspect", "internal_matches?", "match", "match_at_byte_index", "matches?", "matches_at_byte_index?", "name_table", "options", "source", "to_s"]
String.methods                   # => ["%", "*", "+", "<=>", "==", "=~", "[]", "[]?", "ascii_only?", "blank?", "byte_at", "byte_at?", "byte_delete_at", "byte_index", "byte_index_to_char_index", "byte_slice", "byte_slice?", "bytes", "bytesize", "calc_excess_left", "calc_excess_right", "camelcase", "capitalize", "center", "char_at", "char_bytesize_at", "char_index_to_byte_index", "chars", "check_no_null_byte", "chomp", "clone", "codepoint_at", "codepoints", "compare", "count", "delete", "delete_at", "downcase", "dump", "dump_char", "dump_hex", "dump_or_inspect", "dump_or_inspect_char", "dump_or_inspect_unquoted", "dump_unquoted", "dup", "each_byte", "each_byte_index_and_char_index", "each_char", "each_char_with_index", "each_codepoint", "each_grapheme", "each_grapheme_boundary", "each_line", "empty?", "encode", "ends_with?", "find_start_and_end", "grapheme_size", "graphemes", "gsub", "gsub_append", "gsub_ascii_char", "has_back_references?", "hash", "hexbytes", "hexbytes?", "includes?", "index", "insert", "insert_impl", "inspect", "inspect_char", "inspect_unquoted", "just", "lchop", "lchop?", "lines", "ljust", "lstrip", "match", "matches?", "partition", "presence", "pretty_print", "rchop", "rchop?", "remove_excess", "remove_excess_left", "remove_excess_right", "reverse", "rindex", "rjust", "rpartition", "rstrip", "scan", "scan_backreferences", "scrub", "single_byte_optimizable?", "size", "size_known?", "split", "split_by_empty_separator", "split_single_byte", "squeeze", "starts_with?", "strip", "sub", "sub_append", "sub_index", "sub_range", "succ", "titleize", "to_f", "to_f32", "to_f32?", "to_f64", "to_f64?", "to_f?", "to_f_impl", "to_i", "to_i128", "to_i128?", "to_i16", "to_i16?", "to_i32", "to_i32?", "to_i64", "to_i64?", "to_i8", "to_i8?", "to_i?", "to_s", "to_slice", "to_u128", "to_u128?", "to_u16", "to_u16?", "to_u32", "to_u32?", "to_u64", "to_u64?", "to_u8", "to_u8?", "to_unsafe", "to_unsigned_info", "to_utf16", "tr", "underscore", "unicode_delete_at", "unsafe_byte_at", "unsafe_byte_slice", "unsafe_byte_slice_string", "upcase", "valid_encoding?"]
Thread.methods                   # => ["detach", "event_base", "gc_thread_handler", "gc_thread_handler=", "initialize", "join", "main_fiber", "next", "next=", "previous", "previous=", "scheduler", "stack_address", "start", "to_unsafe"]
Bool.methods                     # => ["!=", "&", "==", "^", "clone", "hash", "to_s", "to_unsafe", "|"]
Int32.methods                    # => ["!=", "&", "&*", "&+", "&-", "*", "+", "-", "/", "<", "<=", "==", ">", ">=", "^", "clone", "leading_zeros_count", "popcount", "to_f", "to_f!", "to_f32", "to_f32!", "to_f64", "to_f64!", "to_i", "to_i!", "to_i128", "to_i128!", "to_i16", "to_i16!", "to_i32", "to_i32!", "to_i64", "to_i64!", "to_i8", "to_i8!", "to_u", "to_u!", "to_u128", "to_u128!", "to_u16", "to_u16!", "to_u32", "to_u32!", "to_u64", "to_u64!", "to_u8", "to_u8!", "trailing_zeros_count", "unsafe_chr", "unsafe_div", "unsafe_mod", "unsafe_shl", "unsafe_shr", "|"]
Float64.methods                  # => ["!=", "*", "**", "+", "-", "/", "<", "<=", "==", ">", ">=", "ceil", "clone", "fdiv", "floor", "next_float", "prev_float", "round_away", "round_even", "to_f", "to_f!", "to_f32", "to_f32!", "to_f64", "to_f64!", "to_i", "to_i!", "to_i128", "to_i128!", "to_i16", "to_i16!", "to_i32", "to_i32!", "to_i64", "to_i64!", "to_i8", "to_i8!", "to_s", "to_u", "to_u!", "to_u128", "to_u128!", "to_u16", "to_u16!", "to_u32", "to_u32!", "to_u64", "to_u64!", "to_u8", "to_u8!", "trunc"]
Proc.methods                     # => ["==", "===", "arity", "call", "clone", "closure?", "closure_data", "hash", "internal_representation", "partial", "pointer", "to_s"]

脚註編集

  1. ^ Contributors”. github.com. 2022年7月18日閲覧。
  2. ^ Brian J., Cardiff (2013年9月9日). “Type inference part 1”. crystal-lang.org. 2022年7月18日閲覧。
  3. ^ Crystalのソースファイルの拡張子は.cr です
  4. ^ crystalコンパイラーを始めとする、crystalの言語処理系(標準ライブラリーを含む)とツールチェインやユーティリティーなどは、crystal自身で書かれています。
  5. ^ Literals
  6. ^ "For" Loop support #830
  7. ^ Macros - Crystal
  8. ^ def p!(*expressions) : Nop
  9. ^ Operatorsaccess-date:2022-07-22
  10. ^ for も do-while も loop もありません。

外部リンク編集