僕は、Rubyistだ。Rubyと、そのコミュニティ、その生産性など、Rubyにまつわる多くのものがとにかく大好きだ。僕がRuby専門で書いてきて現在で4年以上になる。そして、これからも続けていきたい。だが言語やツールはいずれ置き換えられていくということもわかっている。 Rubyは素晴らしいが、必ずしもその速さで知られているわけではない。時に、要求が厳しいアプリケーションには適さないこともある。また、Ruby 3×3が出るまでしばらく待たなければならない。
ここで、あなたに質問がある:
「あなたは、Rubyのように滑らかで、Cのように速いプログラミング言語を夢見たことはあるだろうか?」
正直に言って、僕はそういうものを常に夢見ていたし、なぜそれが存在しないのだろうと思っていた。そんなことを思っていると、Crystalを見つけた。まだその日のことをはっきり覚えている。それは2015年7月だった。僕は/r/programming(訳注: Reddit のプログラミング板)を読んでいて、「Crystal: Cのように速く、Rubyのように滑らか」といったようなものを見た。
だからそのサイトに行って、Crystalをダウンロードし、僕の初となるCrystal(実質Ruby)プログラムを走らせた。
crystal hello.rb
p "Hello World"
そうしたら動いたんだ!
Hello World
もう今までで最高に幸せな【Hello World】だった。
Crystalを始める
さて、Crystalの目標をいくつか詳しく見てみるとしよう。
- Rubyに似た構文を持っている(ただし、Rubyとの互換性はその目的ではない)。
- 静的に型チェックされるが、変数またはメソッドの引数の型を指定する必要はない。
- ボイラープレートコード(テンプレート的なコード)を避けるため、コンパイル時評価とコード生成がある。
- 効率的なネイティブコードにコンパイルする。
ここで強調しなければならない重要な点がいくつかある。 まず、
Rubyに似た構文を持っている(ただし、Rubyとの互換性はその目的ではない)。
これは説明するまでもない。CrystalはRubyではない。Railsを実行することはできない。
静的に型チェックされるが、変数またはメソッドの引数の型を指定する必要はない。
Rubyとは違ってCrystalは型付き言語だが、ほとんどの場合型を指定する必要はない。たとえば、これを例に取ろう。
def greet(name, age)
"I'm #{name}, #{age} years old."
end
greet "Serdar", 27 # I'm Serdar, 27 years old.
じゃあ、いつ何のために型を使うのか?
def add(x : Number, y : Number)
x + y
end
\# Ok
add 1, 2 # Ok
\# Error: no overload matches 'add' with types Bool, Bool
add true, false
素晴らしい、コンパイル時エラーだ。x、yの型だけを**【Number】**として受け入れるようにメソッドを制限する。Rubyだと、これは実行時エラー、すなわち最悪の事態になるだろう。いいぞCrystal!
ボイラープレートコード(テンプレート的なコード)を避けるため、コンパイル時評価とコード生成がある。
マクロ好きな人~?Rubyはメタプログラミング機能で有名だ。Crystalはマクロを使用してボイラープレートコードを減らしてくれる。以下の例はCrystalの素晴らしいWebフレームワーク、Kemalのものだ。
HTTP_METHODS = %w(get post put patch delete options)
{% for method in HTTP_METHODS %}
def {{method.id}}(path, &block : HTTP::Server::Context -> _)
Kemal::RouteHandler::INSTANCE.add_route({{method}}.upcase, path, &block)
end
{% end %}
KemalでDSL宣言がどのように行われているかがこちらだ。配列**【HTTP_METHODS】**をループして各HTTPメソッドのメソッドを定義している。ところで、マクロはコンパイル時に評価される。つまり、パフォーマンスに不利な条件はない。
効率的なネイティブコードにコンパイルする。
Crystalはコンパイラ型言語だ。僕はコンパイラを持っている利点に深く突っ込んでいくつもりはないが、確実に言えるのは、それが多くの最適化を無料で与えてくれるということだ。さらに、Crystalプログラムをコンパイルすると、効率的な単一ファイルのネイティブコードになる。すごく便利で簡単に実行/展開できる。
Crystalプログラムをコンパイルする方法は次の通りだ。
crystal build program.cr
これは、実行可能なものと同じ名前で、実行可能な単一のネイティブバイナリを生成する。
./program
すごい!
Crystalの素晴らしい標準ライブラリ
Crystalには素晴らしい標準ライブラリとツールが搭載されている。最近のアプリケーション構築に必要なすべてのものが揃っている。CSV、YAML、JSON、HTTP、さらにはWebSocketもCrystal自体に同梱されているので、何かを構築し始める際にはすごく簡単だ。
Webサーバが必要だって?大丈夫!
\# server.cr
require "http/server"
server = HTTP::Server.new(8080) do |context|
context.response.content_type = "text/plain"
context.response.print "Hello world, got #{context.request.path}!"
end
puts "Listening on http://0.0.0.0:8080"
server.listen
たった5行(LOC)で、機能的なwebサーバ:**【crystal server.cr】**を構築できる。 **【localhost:8080】**にアクセスしよう。
【crystal】コマンド自体もすごく便利だ。利用可能な組み込みのコードフォーマッタ:【crystal tool format your_app.cr】もある。 最も驚くべき素晴らしいコマンドは【crystal play】だ。これは要するにプレイグラウンドで、Crystalコードをすばやく実行して即時のフィードバックを得ることができる。では、それを実行して【localhost:8080】に移動しよう。
Crystalのパフォーマンス
Crystalは、Rubyのように滑らかでパフォーマンスは高いという独特の目標を持っている。
僕はベンチマークが大好きだが、ベンチマークを疑ってかかるべきだとも思っている。
これはCrystalのための単純なフィボナッチ実装だ(有効なRubyコードでもある):
\# fib.cr
def fib(n)
if n <= 1
1
else
fib(n - 1) + fib(n - 2)
end
end
puts fib(42)
これを実行してどのくらいかかるか見てみよう!
time crystal fib.cr
433494437
crystal fib.cr 2.45s user 0.33s system 98% cpu 2.833 total
これは有効なRubyコードでもあるので、今回はRubyでも実行してみよう。
time ruby fib.cr
433494437
ruby fib.cr 38.49s user 0.12s system 99% cpu 38.718 total
Crysitalは2.833秒で完了。 Rubyは38.718秒で完了。かなりすごい。無料で20倍のパフォーマンスが得られるのだ。最適化を有効にしてプログラムをコンパイルしたらどうなるだろうか?
crystal build --release fib.cr
time ./fib
433494437
./fib 1.11s user 0.00s system 99% cpu 1.113 total
1.113秒。今回はRubyよりほぼ35倍も速い。それってすごくない?そうこなくちゃ!Crystalコンパイラは、LLVMを使用して優れた最適化を行っている。
あなたが(僕のように)ベンチマークに関心があるのなら、crystal-benchmarks-gameレポジトリをチェックするといい。
注意:「The cake is a lie(ケーキは嘘) 」、ベンチマークも同じだ。常にパフォーマンスが35倍向上するということはないが、複雑なアプリケーションでは5倍以上、CPU集中型ではそれ以上のパフォーマンスが期待できる。
Crystalの同時並行性
Crystalでは、メインの実行をブロックすることなく、バックグラウンドで何かを動作させる(別名async)には、キーワード**【spawn】を使用する。これを実現するために、【spawn】**はFiberと呼ばれる軽量のスレッドを作成する。Fiberの作成は非常に安価で、その実行はプロセスによって内部的に管理される。単一のコアに何万ものFiberを簡単に作成することができる。
さて、僕たちはバックグラウンドで稼働させるために**【spawn】を使うことができるが、Fiberから何かを送受信するにはどうすればいいのだろうか?ここで登場するのがChannelだ。Goの【Channels】**に精通している方なら、すっかり馴染みがあるように感じるだろう。
FiberはこのChannelを介し、実行し、メッセージを送信し続けることができる。同じChannelから受信されそうな人に実行制御が与えられる。そのうちの1つが受信、実行されると、他のスポーンされたFiberが実行できるように、スケジューラに制御が戻される。このようにして「ping」と「それに対する返信(ponging)」をし続けることができる。
ピンポンと言えば、「Go by Example」サイトからのこんな抜粋がある。
package main
import "fmt"
func ping(pings chan<- string, msg string) {
pings <- msg
}
func pong(pings <-chan string, pongs chan<- string) {
msg := <-pings
pongs <- msg
}
func main() {
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "passed message")
pong(pings, pongs)
fmt.Println(<-pongs)
}
そしてCrystalバージョン:
def ping(pings, message)
pings.send message
end
def pong(pings, pongs)
message = pings.receive
pongs.send message
end
pings = Channel(String).new
pongs = Channel(String).new
spawn ping pings, "passed message"
spawn pong pings, pongs
puts pongs.receive # => "passed message"
僕にとっては、Crystalのバージョンのほうがより自然で読みやすい。僕は、ほとんどのRubyistが同じように感じるのではないかと思っている。
CrystalはRubyistのためだけじゃない
Crystalはシンプルで学びやすく高性能な一般的なプログラミング言語であり、妥協することなくこれらすべてが独自に組み合わさっている。CrystalはRubyistのためだけのものじゃないのだ!
Crystalで何を作ることができるだろうか?ゲーム、グラフィックレンダラ、低水準エージェント、Webアプリケーションなど。次の輝くプロジェクトがCrystalになるのは本当にあなた次第だ! Crystalで構築されたプロジェクトを調べたい場合は、crystalshards.xyzをチェックしよう。 Crystalのプロジェクトを見つけるにはもってこいの場所だ。
おまけ:次のプロジェクト名が見つからない場合は、ネームジェネレータを試してみよう!
リソース
ここまで来たあなたは、「次はどこに行けばいいの?」と疑問に思っているかもしれない。もちろん、公式のCrystal本は学習を始めるのにうってつけだ。Crystalへの旅をブートしてくれる無料の本に、Crystal for Rubyists(RubyistのためのCrystal)がある。また、Crystalにはコミュニケーションをするための活発なGitterルームがある。そこで会おう!
原文:https://blog.codeship.com/an-introduction-to-crystal-fast-as-c-slick-as-ruby/(2017-4-18) ※元記事の筆者には直接翻訳の許可を頂いて、翻訳・公開しております。