Rustに挫折しました(2度目)
ちょっと他の言語を勉強して気分転換してみようと思い、Crystalをインストールしてみました。

The Crystal Programming Language

2014年に登場したらしく、かなり新しいプログラミング言語です。
Rubyっぽいシンタックスの静的型付き言語、ということです。Rubyを触ったことが無いので本当にゼロからの入門です。

サポートするプラットフォームは、Linux、macOS、そしてWindowsではなくてWSL、というところがなんだか今風ですね。
私はLinux Mint上で作業していますので、WSL上でCrystalを試してみたい、という方はこちらのブログが参考になります。
【Crystal】環境構築(Windows Subsystem for Linux)

インストール

これまた今風にOS標準のパッケージ管理ツールでインストールできるようになっています。
が、古いバージョンに簡単に切り替えたりしたい場合に、それだと不便なので自分は手動でインストールしたい派です。
PHPみたいに言語自体をビルドすることも出来ますが、公式がちゃんとLinux用にCrystalの各バージョンごとにビルドしたものを用意してくれているのでそれを利用します。

[koji:crystal]$ pwd
/home/koji/opt/crystal
[koji:crystal]$ wget https://github.com/crystal-lang/crystal/releases/download/0.31.0/crystal-0.31.0-1-linux-x86_64.tar.gz
[koji:crystal]$ tar zxvf crystal-0.31.0-1-linux-x86_64.tar.gz
[koji:crystal]$ ln -s crystal-0.31.0-1 crystal

パスを通す

自分は.zshenvを利用します。

export CRYSTAL_HOME="$HOME/opt/crystal/crystal"
...
path=(
    ...
    $CRYSTAL_HOME/bin
    ...
)

で、source ~/.zshenvで反映させれば完了!

[koji:crystal]$ source ~/.zshenv
[koji:crystal]$ crystal --version
Crystal 0.31.0 [8d4715a59] (2019-09-23)

LLVM: 8.0.0
Default target: x86_64-unknown-linux-gnu
[koji:crystal]$ 

コードを書いてみる

まだしっかりドキュメントを読んでいない状態ですが、ぱぱっと流し読みしながら挑戦してみます。

HelloWorld

以下のようなファイルをhelloworld.crというファイル名で作成します。

puts "こんにちわ世界!

後はcrystal helloworld.crと実行すれば、コンパイルと実行を同時に行ってくれます。
なお、runがデフォルトなので、runは省略できます。

[koji:crystal]$ crystal run helloworld.cr
こんにちわ世界!
[koji:crystal]$ crystal helloworld.cr 
こんにちわ世界!

このファイルに1行だけで実行できる気軽さ、これはApache Groovyを使っていた時の気軽さと同じものを感じます!良い感じですね。

静的型付け言語なら型指定させてくれるよね!?ということで試してみました。

a: Int32 = 100
puts(a)
[koji:crystal]$ crystal helloworld.cr 
100
[koji:crystal]$ 

エラーになること無く普通に実行できました。
サンプルコードをぱぱっと流し読みすると、変数宣言に型の指定をしていないので、Clojureみたいにコード上に自分で型指定できないのかな、と思っちゃいました。
ちゃんと型指定が出来るんですね。良い感じです。

では、型の異なる値を突っ込んでみます。

a: String = 100
puts(a)
[koji:crystal]$ crystal helloworld.cr
Showing last frame. Use --error-trace for full trace.

In helloworld.cr:1:1

 1 | a: String = 100
     ^
Error: type must be String, not Int32
[koji:crystal]$ 

これです!これです!素晴らしい。ちゃんと型が合わない旨表示しながらコンパイルエラーになりました。良い感じです。

同じ変数名を使うと

さて、Crystalではletやらconstみたいな変数を宣言する際のプレフィックスは必要ないようです。
では以下のようなコードはどうなるでしょう?

a = 100
a = "a"
puts(a)

実行すると、

[koji:crystal]$ crystal helloworld.cr
a
[koji:crystal]$ 

いわゆる、スクリプト言語ぽっい動きになりました。良い感じです。
では型を指定してみます。

a: Int32 = 100
a: String = "hoge"
puts(a)
[koji:crystal]$ crystal helloworld.cr
Showing last frame. Use --error-trace for full trace.

In helloworld.cr:2:1

 2 | a: String = "hoge"
     ^
Error: variable 'a' already declared
[koji:crystal]$ 

今度は、既に同名変数(a)が宣言されているよ、とコンパイルエラーにしてくれました。
完璧に「当然そうなるよね?」という動作をしてくれました。良い感じです!!

FizzBuzz

例のごとくFizzBuzzです。
型は基本的に省略せずに書きました。(個人的に型は極力書きたい)

list: Array(Int32) = Array(Int32).new(100, 0)
list2: Array(Int32) = list.map_with_index{|v, i| v + i+1}
puts(list) #  [0, 0, 0, ...省略...  0, 0, 0]
puts(list2) # [0, 1, 2, ...省略... 98, 99, 100]

 # Fizz、Buzz、FizzBuzz判断用クロージャの宣言
fizz = -> (v: Int32) {v % 3 == 0}
buzz = -> (v: Int32) {v % 5 == 0}
fizz_buzz = ->(v: Int32) {v % 15 == 0}

 # 後はループして実行
list2.each{|v|
    tuple: Tuple(Bool, Bool, Bool) = {fizz.call(v), buzz.call(v), fizz_buzz.call(v)}
    case tuple
    when {true, false, false} then puts("Fizz")
    when {false, true, false} then puts("Buzz")
    when {true, true, true } then puts("FizzBuzz")
    else puts(v)
    end
}

実行すれば正しくFizzBuzzが表示されます。
一点気になったのが、caseでは、when部分のTupleにはアンダーバー_が使えるよ、と公式ドキュメントに書いていますが、どうも上記のサンプルだとコンパイルエラーになってしまったので諦め直接値を記述しました。
もしwhen {_, _, true}と記述すると、Error: can't read from _というコンパイルエラーになります。

その他

vimプラグイン

公式でvim-crystalへのリンクがされていたのでそれを利用します。
私はNeoBundleを使っているので、.vimrcに以下を追記するだけです。

NeoBundle 'rhysd/vim-crystal'

これでシンタックスハイライトも効きますし、vimでvim-quickrunを導入しているのであればvimから直接Crystalを実行することも出来るようになります。

REPLはないけどブラウザで試せる

どうやら標準ではREPLは無いようですが、ブラウザ上でREPLと同じ事が出来る機能が標準で搭載されています。
一回ブラウザを開いてアクセスする、という面倒臭さがありますが、エディタを開くよりは楽かも?
crystal playというコマンドを叩けば簡単に起動します。

[koji:crystal]$ crystal play -h 
Usage: crystal play [options] [file]

Options:
    -p PORT, --port PORT             Runs the playground on the specified port
    -b HOST, --binding HOST          Binds the playground to the specified IP
    -v, --verbose                    Display detailed information of executed code
    -h, --help                       Show this message
[koji:crystal]$ crystal play -p 8081
Listening on http://127.0.0.1:8081

これでブラウザでhttp://127.0.0.1:8081にアクセスすればよくあるPlaygroundが確認できます。

実行ファイルを生成

今までコンパイルと実行を同時に行っていましたが、buildコマンドを使うと、RustやGoの用にお手軽に実行バイナリを生成できます。

[koji:crystal]$ crystal build helloworld.cr
[koji:crystal]$ ls
helloworld  helloworld.cr
[koji:crystal]$ ./helloworld               
hello world
[koji:crystal]$ 

実行しようとするとエラー

HelloWorldの辺りでは言及しませんでしたが、実は初めてCrystalのコードを実行しようとしたら以下のエラーが発生しました。

[koji:crystal]$ crystal helloworld.cr
/usr/bin/ld: -levent が見つかりません
collect2: error: ld returned 1 exit status
Error: execution of command failed with code: 1: `cc "${@}" ...長いので省略

/usr/bin/ld: -leventなので、libeventライブラリが無いと言うエラーですね。
無い事無いでしょ?と思って調べて見ました。

libevent自体はある?
[koji:lib]$ sudo ldconfig -p | grep libevent
        libevent-2.1.so.6 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libevent-2.1.so.6
移動して中身を確認
[koji:lib]$ cd /usr/lib/x86_64-linux-gnu
[koji:x86_64-linux-gnu]$ ls -al | grep event
lrwxrwxrwx   1 root root       21 10月  8  2018 libevent-2.1.so.6 -> libevent-2.1.so.6.0.2
-rw-r--r--   1 root root   330128  2月  5  2018 libevent-2.1.so.6.0.2
lrwxrwxrwx   1 root root       23  5月 23 14:06 libtevent-util.so.0 -> libtevent-util.so.0.0.1
-rw-r--r--   1 root root    10160  5月 23 14:06 libtevent-util.so.0.0.1
lrwxrwxrwx   1 root root       19 10月  8  2018 libtevent.so.0 -> libtevent.so.0.9.34
-rw-r--r--   1 root root    59944 11月 23  2017 libtevent.so.0.9.34
[koji:x86_64-linux-gnu]$ 

どうもlibevent.soが見当たりません。
自分でsudo ln -s libevent-2.1.so.6.0.2 libevent.soを実行してしまっても良かったのですが、コレだとlibeventがアップグレードされた際に自分でシンボリックリンクを貼り直すのが面倒です。
とうことで普通にsudo apt install libevent-devを実行しちゃいました。
するとちゃんとlibevent2にシンボリックリンクも貼ってくれました。

[koji:x86_64-linux-gnu]$ ls -al | grep libevent
lrwxrwxrwx   1 root root       21 10月  8  2018 libevent-2.1.so.6 -> libevent-2.1.so.6.0.2
-rw-r--r--   1 root root   330128  2月  5  2018 libevent-2.1.so.6.0.2
-rw-r--r--   1 root root   585008  2月  5  2018 libevent.a
lrwxrwxrwx   1 root root       21  2月  5  2018 libevent.so -> libevent-2.1.so.6.0.2
lrwxrwxrwx   1 root root       26  2月  5  2018 libevent_core-2.1.so.6 -> libevent_core-2.1.so.6.0.2
-rw-r--r--   1 root root   210960  2月  5  2018 libevent_core-2.1.so.6.0.2
-rw-r--r--   1 root root   370694  2月  5  2018 libevent_core.a
lrwxrwxrwx   1 root root       26  2月  5  2018 libevent_core.so -> libevent_core-2.1.so.6.0.2
lrwxrwxrwx   1 root root       27  2月  5  2018 libevent_extra-2.1.so.6 -> libevent_extra-2.1.so.6.0.2
-rw-r--r--   1 root root   137520  2月  5  2018 libevent_extra-2.1.so.6.0.2
-rw-r--r--   1 root root   214386  2月  5  2018 libevent_extra.a
lrwxrwxrwx   1 root root       27  2月  5  2018 libevent_extra.so -> libevent_extra-2.1.so.6.0.2
lrwxrwxrwx   1 root root       29  2月  5  2018 libevent_openssl-2.1.so.6 -> libevent_openssl-2.1.so.6.0.2
-rw-r--r--   1 root root    26536  2月  5  2018 libevent_openssl-2.1.so.6.0.2
-rw-r--r--   1 root root    26682  2月  5  2018 libevent_openssl.a
lrwxrwxrwx   1 root root       29  2月  5  2018 libevent_openssl.so -> libevent_openssl-2.1.so.6.0.2
lrwxrwxrwx   1 root root       30  2月  5  2018 libevent_pthreads-2.1.so.6 -> libevent_pthreads-2.1.so.6.0.2
-rw-r--r--   1 root root    10008  2月  5  2018 libevent_pthreads-2.1.so.6.0.2
-rw-r--r--   1 root root     4814  2月  5  2018 libevent_pthreads.a
lrwxrwxrwx   1 root root       30  2月  5  2018 libevent_pthreads.so -> libevent_pthreads-2.1.so.6.0.2
[koji:x86_64-linux-gnu]$ 

まとめ

Rustの勉強に行き詰まってしまったので、静的型付けで、ScalaやKotlin、ElixirみたいなVMを挟まずに直接バイナリを生成する新し目の言語は他に無いかな、と思っていたらこのCrystalを教えてもらいました。
main関数とか書かずともササッと1行だけで実行できるこの感覚、久しく忘れていたApache Groovyを勉強し始めたの頃のワクワク感を思い出します!

触っていて気になったのが、ちょっとコンパイルが遅いな、という点と、新しい言語なのになんで標準でこの機能がないの?!というのが何点かありまいた。(とくにmatchとか)
とは言え、これからどんどん成長していくはずですし、自分自信が見落としている部分が山ほど有る、というか公式のチュートリアルさえまだちゃんと読んでいないので、もっと触っみないと分からないですね。

第一印象としては、かなり好印象です!
途中で何度も良い感じと強調しましたが、本当になんだか触っていて自然に体に馴染んでくるような感じがして、良い感じです。
これはApache GroovyとDartを勉強したり使っていた時に感じた、あの良い感じです。
この感覚を持てるということは、Crystalは自分に合っているような気がします。
途中でやーめた、とならないように、引き続き勉強を進めていこうと思います。