プログラミング言語CrystalでHello World
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は自分に合っているような気がします。 途中でやーめた、とならないように、引き続き勉強を進めていこうと思います。
公開日:2019/09/27