2018年11月1日 | ブログ記事

Crystal lang

MENTOS

この記事はtraPアドベントカレンダー8日目の記事です。

初めまして、traPの飯島ゆん担当の飯島ゆんです。可愛い女の子なので、現実世界でも可愛い女の子になりたいと思い、カスタムキャストで自分のなりたい女の子を作りました。

女の子の悩み

ところで、女の子なら誰でも抱えている悩みがあると思います。そう、「静的型付けでありながらRubyのように書きやすく、Cのように素早く動作するプログラミング言語はないのか」という悩みですね!!!
かくいう私もその一人で、Rubyの書き味が好きでよく書いているのですが、正直「もう少し早くならないかな〜」と思うことが多々あります。また、Railsなどを書いているとよくRecordが存在せず返ってきたnilに対してmapを呼び出して
隕石
と言った感じで実行時にお亡くなりになることがそこそこあるので、そこらへんを実行せずにコンパイラでチェックしてもらえると嬉しいですよね?ただ、それのせいで書き味が犠牲になるのは嫌です。
果たして人類が絶滅するまでの間に、このような女の子の願望を満たす言語は現れるのでしょうか?(いや、現れない)








神への祈り

私はプログラミング言語の神に長い間祈り続けました。その間私は朝昼晩とおやつ以外は何も食べず、飲み物も1日に1,800mlしか飲まず、さらには音ゲーも1日12クレ(600円)しかしないという過酷な環境に身を置き続けました。

(祈りっている時の様子)

そのような私を神が憐れんだのか、ついにある言語の存在が啓示されました。そう、その言語こそが……

Crystal lang

というわけで、前置きはここら辺にしてCrystal言語の紹介やを触ってみての感想などを書いていきたいと思います。

What is Crystal lang?

Crystal langとはどのような言語なのでしょうか?本家のドキュメントを見てみると次のように書かれています。

Crystal is a programming language with the following goals:

  • Have a syntax similar to Ruby (but compatibility with it is not a goal).
  • Be statically type-checked, but without having to specify the type of variables or method arguments.
  • Be able to call C code by writing bindings to it in Crystal.
  • Have compile-time evaluation and generation of code, to avoid boilerplate code.
  • Compile to efficient native code.

私は日本語も英語もプログラミング言語も読めないタイプの女の子なので上の英文を適当に訳すと、

Crystal langは

  • Rubyに似た構文で
  • 静的型付けだけど変数や引数に型を明示する必要がなく
  • Cの遺産をCrystalから使用できて
  • コンパイル時にコード生成ができて
  • 実行可能で効率的なバイナリにコンパイルされる
    ことを目標とした言語である

と言った感じになります。
ここで、私のような女の子の欲しい言語がなんだったかを思い出してみると、

そう、Crystal langはこれらの希望にマッチしています!それだけでなく、Rubyで慣れ親しんだメタプログラミングほどとは行かないまでも、ある程度柔軟な動作もできるのです。

ここまでの紹介でなんとなくCrystal langの目指す雰囲気はふわっと伝わったかと思いますが、実際はどんな感じなのかをコードを交えて見ていきたいと思います。

Rubyに似た構文

Crystal langとRubyの構文を比較する上で面白そうな例を書いてきました。
以下に示すのはRubyにおけるFizzBuzzの例です。

fizzbuzz.rbdef fizzbuzz
  (1..16).each do |i|
    if fizz?(i) && buzz?(i)
      p "fizzbuzz"
    elsif fizz?(i)
      p "fizz"
    elsif buzz?(i)
      p "buzz"
    else
      p i
    end
  end
end

def fizz?(n)
  (n % 3).zero?
end

def buzz?(n)
  (n % 5).zero? 
end

fizzbuzz

特に特筆するところもない普通のFizzBuzzですね。
それでは、このFizzBuzzをCrystal langで書き直してみるとどうなるでしょうか?
どれくらい似た結果になるのかを予想してみてください!


































fizzbuzz.crdef fizzbuzz
  (1..16).each do |i|
    if fizz?(i) && buzz?(i)
      p "fizzbuzz"
    elsif fizz?(i)
      p "fizz"
    elsif buzz?(i)
      p "buzz"
    else
      p i
    end
  end
end

def fizz?(n)
  (n % 3).zero?
end

def buzz?(n)
  (n % 5).zero? 
end

fizzbuzz

完全に一致しましたね。「コピペミスじゃないのか」ですって?とんでもない、実際に実行しても2つは完全に同じ動作をします。

ruby grammer.rb
1
2
"fizz"
4
"buzz"
"fizz"
7
8
"fizz"
"buzz"
11
"fizz"
13
14
"fizzbuzz"
16
crystal grammer.cr #ここでは実行可能ファイルを作成せずにそのまま実行しています
1
2
"fizz"
4
"buzz"
"fizz"
7
8
"fizz"
"buzz"
11
"fizz"
13
14
"fizzbuzz"
16

これらの結果を見て、いかにCrystal langの文法がRubyとよく似ているかを感じ取っていただけると思います。
もちろんCrystal langとRubyとでライブラリ名が違うことや、Crystal langが静的型付けであることなどが原因で、全てのRubyのコードがそのままCrystal langで動くわけではありませんが(そもそもRubyとの互換性を保つために文法を似せているわけではないのでそれはそう)、Rubyに慣れている人にとっては違和感なく読み書きのできる言語になっています。

実行速度

女の子はせっかちなので、実行速度は大事な要素です。Rubyに対する不満として最初にあげた通り、私も例に漏れずせっかちな女の子です。果たしてCrystal langは十分に高速なのでしょうか?

今回は実行速度を求めるために45番目のフィボナッチ数を求めるプログラムを書き、その実行時間を計測しました。今回は定義通りの再帰を用いた方法でフィボナッチ数を求めてみました。このアルゴリズムだと計算量はo(2n)o(2^n)なので、簡単に実行時間を爆発させることができます。

なお、今回は比較対象としてRubyおよびC言語でも計測してみました。実際のコードは以下の通りです。

fib.crdef fib(n)
  return n if n < 2
  fib(n - 2) + fib(n - 1)
end

p fib(45)
fib.rbdef fib(n)
  return n if n < 2
  fib(n - 2) + fib(n - 1)
end

p fib(45)
fib.c#include <stdio.h>
int fib(int n) {
  if (n < 2) {
    return n;
  }
  return fib(n - 2) + fib(n - 1);
}

int main(int argc, char *argv[]) {
    printf("%d\n", fib(45));
    return 0;
}

では、実際の実行時間をみてみましょう。今回はtimeコマンドを用いて実行時間を計測してみました。
まずはRubyの場合です。

time ruby fib.rb
1134903170
      113.89 real       112.91 user         0.28 sys

smile

大体114秒という結果になりました。もしあなたがビルゲイツだったら、この待ち時間で600万円稼げます。そうでなくても音ゲーで1譜面遊べてしまいます。

次はCの場合をみてみましょう。

time gcc -O2 fib.c
        0.06 real         0.03 user         0.02 sys
time ./a.out
1134903170
        5.49 real         5.43 user         0.01 sys

今回はなんとなく最適化してみました(最適化前と比べて1秒ほど早くなりました)。コンパイルにかかる時間を合わせても大体5.5秒とRubyと比べるとかなり早くなりました。これぐらいの実行時間ならかなり高速と言えるのではないでしょうか?

最後にCrystal langの場合はどうでしょうか。

time crystal build fib.cr
        0.52 real         0.50 user         0.12 sys
time ./fib
1134903170
        5.34 real         5.26 user         0.01 sys

コンパイルにかかった時間がCより長いので合計としてはCに軍配が上がりましたが、単純な実行時間だけを比較するとCrystal langの方が若干高速という結果になりました。

今回の結果により、Crystal langは十分に高速であることがわかっていただけたかと思います。

コンパイラによるチェック

ここまでを読んできた方にはわかっていただけると思いますが、Crystal langのプログラムはRubyのそれとかなり似通っています。なんなら今回挙げた例は全て同じコードになっています。これで本当にコンパイラが型に関するエラーをチェックしてくれるのでしょうか?

次に示すのはRailsなどでよくある、条件にあったレコードを持ってきてそれに対して処理を行うコードの簡略化した例です。
recordsという集合の中で条件にマッチしたものを取ってきて、それらに対して最初の文字を大文字にするという同一の処理を行なっています。

def some_condition
  rand(2).even?
end

records = "some_record" if some_condition
p records.capitalize

ここで、some_condition == trueの場合は無事にcapitalizeを呼び出してレコードの先頭を大文字にすることができます。

ruby nil.rb
"Some_record"

しかし、some_condition == falseの場合にはどうなるでしょうか?この場合だとrecords == nilとなってしまい、capitalizeを呼び出そうとした段階で次のような感じで終焉を迎えます。

Traceback (most recent call last):
nil.rb:6:in `<main>': undefined method `capitalize' for nil:NilClass (NoMethodError)

今回の例のコードはちょっと簡略化しすぎているのであれですが、Railsなどを使っていると"条件にあう要素がなかった場合はnilを返す"メソッドを使用する機会が多々あります。
それに対して何も考えずに処理を行おうとして実行時に爆発することはできれば避けたいことですが、人類の多くは鶏なので3歩歩くうちにそんなことを忘れてそのまま処理を行います。そして無残な姿を晒すことになります。
そのようなことを避けるためにもテストをしっかり書いていくことは大事ですが、そんなことをしなくても機械的にチェックしてもらえたら嬉しいですよね?

Crystal langの場合はこうなります。

def some_condition
  rand(2).even?
end

records = "some_record" if some_condition
p records.capitalize

上記のコードを実行しようとすると、

crystal build nil.cr
Error in nil.cr:6: undefined method 'capitalize' for Nil (compile-time type is (String | Nil))

p records.capitalize
          ^~~~~~~~~~

こんな感じでコンパイル時に怒られます。
これはどういう風に怒られているかというと、「recordsという(String | Nill)型(String型かNilという意味)の変数のNilにはcapitalizeメソッドはないよ!」って感じです。これを直すまではプログラムの実行ができないので、実行時にどかーんすることが無くなります。いいですね。

なお、上記のプログラムのコンパイルを通したければこんな感じになります。

def some_condition
  rand(2).even?
end

records = "some_record" if some_condition
p records.capitalize if records.is_a?(String)

まとめ

ここまでいくつかCrystal langの魅力について話してきました。本当はまだまだ話すことはあると思うのですが、来週の論文輪講の準備がやばいのでこれくらいにしておきます。この記事を読んで面白そうだなと思った方はぜひ見て触れて驚いて見てください!!!

この記事を書いた人
MENTOS

traPではマインスイーパーとやったこと無いけど音楽ゲームとバドミントンを担当しています。最近はもっぱらJavascriptの闇に飲まれています。常にミルクティーを飲んでないと生きていけない体にされました。ゆりはいいぞ ・好きな言語: Ruby ・勉強中: Rails, Scala, イラスト ・好きなミルクティー: Lipton

この記事をシェア

このエントリーをはてなブックマークに追加

関連する記事

2018年11月3日
ERC20トークンを用いた宝探しゲーム(真)の提案【アドベントカレンダー2018 10日目】
Azon
2018年11月16日
最近作ってるゲームの話 【アドベントカレンダー2018 23日目】
ryuon
2018年11月15日
転倒数と15パズル【アドベントカレンダー2018 22日目】
RyoTei
2018年11月15日
君だけの「にゃーんボタン」を作ろう!
nagatech
2018年11月14日
Android Studioの紹介
SoLA
2018年11月14日
MagicaVoxelを触ってみたお話
topaz

活動の紹介

カテゴリ

タグ