これは「Ruby脳にはCrystalつらい Advent Calendar 2015」の8日目の記事です。
Ruby だと、プログラマーが配列の1番目の要素は整数で、2番目の要素が文字列で…といったように決めて、次のようなコードを書いたりすることがあります。
[ [ 1, "hoge" ], [ 2, "fuga" ], [ 3, "piyo" ], ].each do |obj| num, str = obj num + 100 str.size end
Crystal だとこれはコンパイルエラーになります。
% crystal hoge.cr Error in ./hoge.cr:1: instantiating 'Array(Array(String | Int32))#each()' [ ^~~~ in /opt/crystal/src/array.cr:774: instantiating 'each_index()' each_index do |i| ^~~~~~~~~~ in /opt/crystal/src/array.cr:774: instantiating 'each_index()' each_index do |i| ^~~~~~~~~~ in ./hoge.cr:1: instantiating 'Array(Array(String | Int32))#each()' [ ^~~~ in ./hoge.cr:7: no overload matches 'String#+' with types Int32 Overloads are: - String#+(other : self) - String#+(char : Char) num + 100 ^
[ 1, "hoge" ]
として作られた配列は、「要素が Int32 または String である配列」となります。
その要素に対するメソッド呼び出しは、どちらのクラスでも呼び出すことができるメソッドでなければいけません。
String に 100 を引数とした + メソッドは呼び出せないし、Int32 に size メソッドはありません。
1番目の要素が Int32 で 2番目が String というプログラマの勝手な思いは Crystal には届きません。慈悲はない。
このような場合は、Int32 と String のメンバー変数を持つ構造体のようなクラスを使うか、次のように if でクラス判定をすればよいです。
[ [ 1, "hoge" ], [ 2, "fuga" ], [ 3, "piyo" ], ].each do |obj| num, str = obj num + 100 if num.is_a? Int str.size if str.is_a? String end
または、今回の場合は明に型変換をしてもよいかもしれません。
[ [ 1, "hoge" ], [ 2, "fuga" ], [ 3, "piyo" ], ].each do |obj| num, str = obj num.to_i + 100 str.to_s.size end