続・Crystal の小さな本 もあります。
Crystal で Hello World!
Crystal という Ruby ライクな言語は、C や Go の代わりとして使えます。つまり、Ruby と異なりインタプリタでなく実行ファイルを生成するコンパイラ言語です。
インストール方法は Ubuntu の場合こちらのページになります。
2023/9 段階ではバージョン 1.9.2 になっていますが、Windows 版はプレビュー段階ですし、Linux 版も ARM 版はありません。
と言っても内部では C のソースを生成してそれを C コンパイラがビルドして実行ファイルを生成しているようです。
Hello World! のコードは ruby と全く同じで次のようになっています。
puts "Hello World!"
このプログラムをビルドして実行するには次のコマンドを実行します。
$ crystal hello_world.cr
すると、次のようなエラーが出てしまいました。(Ubuntu 23.04LTS で)
/usr/bin/ld: -levent が見つかりません: そのようなファイルやディレクトリはありません
collect2: error: ld returned 1 exit status
Error: execution of command failed with code: 1:cc "${@}" -o /home/user/.cache/crystal/crystal-run-hello.tmp -rdynamic -L/usr/bin/../lib/crystal -lm -lgc -lpthread -levent -lrt -lpthread -ldl
つまり、「event というライブラリがないよ!」 とのことです。
これは、Ubuntu 23.04LTS のインストール時に入ってくれなかったようです。そこで、次のコマンドでインストールします。
$ sudo apt install libevent-dev
そしてもう一度、ビルドを行ったら次のように表示できました。
$ crystal hello_world.cr
Hello World!
$
これだと、実行ファイルはどこかにできているはずですが、このフォルダ内には作られません。実行ファイルが必要な場合は次のようにします。
$ crystal build hello_world.cr
これだと、現在のフォルダに実行ファイル hello_world ができているはずです。
crystal コマンドのヘルプは crystal だけあるいは crystal help で表示できます。
$ crystal
Usage: crystal [command] [switches] [program file] [--] [arguments]
Command:
init generate a new project
build build an executable
clear_cache clear the compiler cache
docs generate documentation
env print Crystal environment information
eval eval code from args or standard input
i/interactive starts interactive Crystal
play starts Crystal playground server
run (default) build and run program
spec build and run specs (in spec directory)
tool run a tool
help, --help, -h show this help
version, --version, -v show version
Run a command followed by --help to see command specific information, ex:
crystal <command> --help
特定のフォルダに実行ファイルを作るには -o オプションを指定します。このオプションも含め build オプションは crystal build –help で表示できます。
$ crystal build --help
Usage: crystal build [options] [programfile] [--] [arguments]
Options:
--cross-compile cross-compile
-d, --debug Add full symbolic debug info
--no-debug Skip any symbolic debug info
-D FLAG, --define FLAG Define a compile-time flag
--emit [asm|obj|llvm-bc|llvm-ir] Comma separated list of types of output for the compiler to emit
-f text|json, --format text|json Output format text (default) or json
--error-trace Show full error trace
-h, --help Show this message
--ll Dump ll to Crystal's cache directory
--link-flags FLAGS Additional flags to pass to the linker
--mcpu CPU Target specific cpu type
--mattr CPU Target specific features
--mcmodel MODEL Target specific code model
--warnings all|none Which warnings to detect. (default: all)
--error-on-warnings Treat warnings as errors.
--exclude-warnings <path> Exclude warnings from path (default: lib)
--no-color Disable colored output
--no-codegen Don't do code generation
-o Output filename
--prelude Use given file as prelude
--release Compile in release mode
-s, --stats Enable statistics output
-p, --progress Enable progress output
-t, --time Enable execution time output
--single-module Generate a single LLVM module
--threads NUM Maximum number of threads to use
--target TRIPLE Target triple
--verbose Display executed commands
--static Link statically
--stdin-filename Source file name to be read from STDIN
Crystal ソースのビルド
次のスクリプトは1つのソースからなる Crystal プログラムをビルドするものです。出力先が ./bin なので、./bin というフォルダを事前に作っておくか、必要ならスクリプトを修正してください。
#!/usr/bin/bash
if [ $# -eq 0 ]; then
echo "Usage: build.sh source (without file extension)"
exit 1
fi
echo "Compiling .."
crystal build -o ./bin/$1 -d $1.cr
if [ $? -eq 0 ]; then
echo ".. OK"
fi
Crystal プロジェクトの作り方
crystal コマンドの init サブコマンドを使って新規プロジェクトを作ることができます。次のコマンド例は “macro_test” というアプリ・プロジェクトを作るものです。
$ crystal init app macro_test
もし、これがライブラリなら init lib とします。
コマンドを実行すると下のようなフォルダとファイルが生成されます。
$ crystal init app macro_test
create /home/user/workspace/Crystal/macro_test/.gitignore
create /home/user/workspace/Crystal/macro_test/.editorconfig
create /home/user/workspace/Crystal/macro_test/LICENSE
create /home/user/workspace/Crystal/macro_test/README.md
create /home/user/workspace/Crystal/macro_test/shard.yml
create /home/user/workspace/Crystal/macro_test/src/macro_test.cr
create /home/user/workspace/Crystal/macro_test/spec/spec_helper.cr
create /home/user/workspace/Crystal/macro_test/spec/macro_test_spec.cr
Initialized empty Git repository in /home/user/workspace/Crystal/macro_test/.git/
src フォルダの下にある macro_test.cr がアプリケーションのソースファイルです。
spec フォルダには spec_helper.cr と macro_test_spec.cr という2つのファイルが生成されます。これらは Crystal におけるテストコードの一種です。
spec_helper.rb ファイルには、spec を実行するために必要な設定が含まれています。spec を利用するためにはこのファイルをインポートします。
macro_test_spec.cr は実際のテストを行うコードで、次のようになっています。
require "./spec_helper"
describe MacroTest do
# TODO: Write tests
it "works" do
false.should eq(true)
end
end
Shards を使う
プロジェクトで標準ライブラリ以外のパッケージを使う必要がある場合は、shard.yml が必要になる。
具体的には 「Shards とは何か?」を参照。
Crystal と Ruby の文法上の違い
Crystal は Ruby と似た文法を取っていますが、次のような違いがあります。
- 静的型付け Crystalは静的型付け言語ですが、Rubyは動的型付け言語です。つまり、Crystalでは変数や式の型を明示的に指定する必要がありますが、Rubyでは型を指定する必要がありません。
- ブロック Crystalではブロックを処理する際に、do..endブロックやprocブロックを使用できます。Rubyではdo..endブロックのみを使用できます。
- 配列 Crystalでは配列の要素に型を指定することができます。Rubyでは配列の要素に型を指定することはできません。
- メソッド Crystalではメソッドをオーバーライドすることができます。Rubyではメソッドをオーバーライドすることはできません。
これらの違いは、CrystalとRubyの処理速度やメモリ使用量に影響を与える可能性があります。一般的に、静的型付け言語は動的型付け言語よりも処理速度が速く、メモリ使用量が少ない傾向にあります。
(Google Bard による)
さらに具体的な内容については「Crystal に for 文がない!」も参照。
Crystal: コマンドライン引数
Ruby ではコマンドライン引数は $* または ARGV で取得できます。
一方、 Crystal では $* はエラーになりました。
つまり、Crystal では Perl 風の $ 変数は使えないものがたくさんあります。
C 言語では argv はコマンド名そのものを含みますが、Crystal の ARGV ではパラメータのみです。つまり、ARGV[0] は最初のパラメータとなります。
下のコードは、コマンドライン引数一覧を表示するものですが、Ruby でも動作します。
if ARGV.size() == 0
STDERR.puts("No arguments")
exit 1
end
ARGV.each {|a| puts(a)}
puts("Done.")
Crystal のデバッグ
Crystal のデバッグですが、lldb と crystal 公式プラグインをインストールした VSCode で可能です。
また、下のように –debug を指定した build が必要です。
$ crystal build --debug until.cr
そして、このビルド結果である実行ファイル、この例では until を VSCode の launch.json に設定します。(下記の例を参照)
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/until",
"args": [],
"cwd": "${workspaceFolder}"
}
]
}
VSCode でこのプログラム until.cr を読み込んで実行すると下の画像のようにデバッグが可能のはずです。
Crystal: p と p! の違い
Crystal のサンプルプログラムで p! という関数をよく見かけます。
Ruby には p という関数がありますが、Crystal にも p はあります。しかし、p! だと何がそれを表示したのかが簡単にわかります。以下に例を挙げます。
a = true
b = false
p! a, b
p a, b
c = false
p !c
p! !c
これを実行すると下のように表示されます。
a # => true
b # => false
true
false
true
!c # => true
Crystal の標準ライブラリは Ruby と完全互換ではない
Crystal の標準ライブラリは Ruby のそれに近いですが、完全互換ではなく「似ている」くらいの互換性しかありません。
Dir クラスで見てみると、Dir.glob、Dir.entries はどちらにもあって互換性もあるようですが、each_child, chilren は Crystal にはありません。
現在のディレクトリを得るメソッドですが、Crystal は current ですが、Ruby は pwd あるいは getwd です。
ユーザディレクトリを得る home というメソッドは Ruby にしかありません。
ディレクトリを変更するメソッドは Crystal では cd ですが、Ruby では chdir です。
全般的に Ruby のほうがメソッドの数は多く「かゆいところに手が届く」ようなメソッドがありますが、Crystal では「これがあればどうにかなる」というメソッドしかありません。
Ruby と互換性を持たせたければ、メソッドを自作する必要があります。
Crystal の文字列
Crystal の文字列は UTF-8 でエンコードされている。そして、String クラスのインスタンスである。
次に String クラスのメソッドの使用例を示す。
# String: https://crystal-lang.org/api/String.html
s = "あいうえ"
p! typeof(s) # String
p! typeof("abcd") # 半角文字だけでも String 型
p! s # 文字列の内容
s = s + "オ" # 結合
p! s # 結合されたか確認
p! s[2] # 文字を抽出
p! typeof(s[2]) # Char 型
p! s.size # 文字数
p! s.bytesize # バイト数
p! s == "あいうえオ" # 比較
p! s <=> "あいうえオ" # 比較
p! s[9]? # []? を使うとインデックスが不正な場合は Nil になる。
p! s[1..3]? #[]? は文字列スライスを返す。(レンジが不正な場合は Nil)
p! s[1, 3] # スライスの開始位置と長さを指定して切り出す。
p! s =~ /う/ # 文字列に文字や部分文字列が含まれている場合は、その位置を返す。(正規表現が使える)
s1 = "A"*4 # 繰り返し
p! s1
n = "1234".to_i32 # 整数に変換
p! typeof(n), n
p! n.to_s # 文字列へ変換
# 結合演算子
s2 = String.build do |str|
str << "hello "
str << 1
end
p! s2
# 文字列のメソッド
p! s2.starts_with?("hel") # 先頭の文字列を判別
p! s2.ends_with?("3") # 終わりの文字列を判別
p! s2.index("ll") # 部分文字列の位置
p! s2.rindex('o') # 後ろから部分文字列の位置を探す
p! s2.reverse() # 文字を逆順にする。
p! s2.upcase() # アルファベットを大文字にする。
p! "\tstrip".strip() # 空白文字を取り除く
p! s2.gsub('l', 'L') # 文字や文字列を置換する
p! s2.sub('l', 'L') # 最初に見つかった文字や文字列を置換する
a = "1,2,3".split(',') # 文字列を分割した配列を得る。
p! a
p! a.join(":") # 文字列配列の要素を文字や文字列で結合する。
このプログラムの実行例を下に示す。
$ ./bin/utf8string
typeof(s) # => String
typeof("abcd") # => String
s # => "あいうえ"
s # => "あいうえオ"
s[2] # => 'う'
typeof(s[2]) # => Char
s.size # => 5
s.bytesize # => 15
s == "あいうえオ" # => true
s <=> "あいうえオ" # => 0
s[9]? # => nil
s[1..3]? # => "いうえ"
s[1, 3] # => "いうえ"
s =~ (/う/) # => 2
s1 # => "AAAA"
typeof(n) # => Int32
n # => 1234
n.to_s # => "1234"
s2 # => "hello 1"
s2.starts_with?("hel") # => true
s2.ends_with?("3") # => false
s2.index("ll") # => 2
s2.rindex('o') # => 4
s2.reverse() # => "1 olleh"
s2.upcase() # => "HELLO 1"
"\tstrip".strip() # => "strip"
s2.gsub('l', 'L') # => "heLLo 1"
s2.sub('l', 'L') # => "heLlo 1"
a # => ["1", "2", "3"]
a.join(":") # => "1:2:3"
Crystal の配列
Crystal の配列は Array クラスのインスタンスです。長さは可変長で要素の型は複数指定可能です。下に Array の使用例を示します。
# 配列 (Array): https://crystal-lang.org/api/Array.html
a = [1, 2, 3]
p a
b = [0, "Data", 1.23, nil] # 型の異なる要素からなる配列
p b
c = Array(Int32).new # 整数配列
p c.size # 長さ
# 要素を追加する。
c << 9
c << -6
c << -5
p c
# 整数配列に型の違う要素を追加する。
# c << "String" はコンパイルエラーになる。
# a << "String" コンパイルエラーになる。
b << "String" # OK
p b
# 整数と文字列を許容する配列
d = Array(Int32 | String).new
d << 0
d << "any"
p d
この例には長さ 0 の配列が含まれていません。他の言語では [] と書くことが多いですが、Crystal では型を指定する必要があるので、そういう書き方はエラーになります。つまり、次のような感じで書きます。
a = Array(Int32).new あるいは [] of Int32
以下にこの実行例を示します。
$ ./bin/array
[1, 2, 3]
[0, "Data", 1.23, nil]
0
[9, -6, -5]
[0, "Data", 1.23, nil, "String"]
[0, "any"]
Array は可変長ですが、固定長の StaticArray クラスもあります。下に StaticArray の使用例を示します。
# 固定長配列 (StaticArray): https://crystal-lang.org/api/StaticArray.html
a = StaticArray(Int32, 4).new(0)
p! a
p! a.size
a[0] = 1
a[3] =10
p! a
b = StaticArray['A', 'B', 'C']
p! b
c = b.clone
p! c == b
b[1] = 'a'
p! b != c
d = b.sort
p! d
この実行例を以下に示します。
$ ./bin/static_array
a # => StaticArray[0, 0, 0, 0]
a.size # => 4
a # => StaticArray[1, 0, 0, 10]
b # => StaticArray['A', 'B', 'C']
c == b # => true
b != c # => true
d # => StaticArray['A', 'C', 'a']
配列のソートについては「Crystal: ソート」の記事参照。
Crystal の標準ライブラリ
Crystal は独自の標準ライブラリを持ちますが、Ruby とは完全互換ではないので注意が必要です。
そもそも、Crystal は静的型付け言語であり、動的に変数の型が変わる Ruby との互換性を保つことはできません。
また、Ruby と比べライブラリの規模は大きくないので、求める関数が見つかるとは限りません。
Crystal 標準ライブラリ (v1.9.2) の構成は次のようになっています。
ここで必要なモジュールが見つからない場合は、ググって見つけるしかないようです。現在 (2023) のところ、RubyGems のようなものはないようです。
ArgumentError
Array
Atomic
Flag
Base64
Error
Benchmark
BM
Job
Tms
IPS
Entry
Job
BigDecimal
(以下略)
Crystal: Shards とは何か?
Crystal には shards というコマンドが付属している。このコマンドは次のようなサブコマンドを持っており、様々な機能がある。
shards build: 実行ファイルをビルドする。
shards check: 依存関係をチェックする。
shards init: 新しい shard.yml を作成する。
shards install: 依存するパッケージをインストールする。
shards list: インストールされたパッケージ一覧を表示する。
shards prune: 使われていないパッケージを削除する。
shards update: パッケージを更新する。
shards version: バージョンを表示する。
shards コマンドの主要機能は、Crystal 標準ライブラリに含まれていない外部パッケージを管理することである。
その管理を行うためのファイルが shard.yml というファイルであり、外部パッケージの場所の情報とそれらを使ったビルド情報が含まれる。
次の例は標準ライブラリに含まれていない SQLite3 のコネクタを使うプロジェクトで使う shard.yml の例である。
name: funa
version: 0.1.0
authors:
- your-name-here <your-email-here>
targets:
funa:
main: src/funa.cr
dependencies:
sqlite3:
github: crystal-lang/crystal-sqlite3
crystal: 1.9.2
license: MIT
この shard.yml をどんな風に使うかと言うと、次のような感じで使用する。
1.プロジェクトを作成する。
この例では、crystal コマンドでサブコマンド init app を使って funa というプロジェクトを作成している。
$ crystal init app funa
create /home/user/workspace/Crystal/funa/.gitignore
create /home/user/workspace/Crystal/funa/.editorconfig
create /home/user/workspace/Crystal/funa/LICENSE
create /home/user/workspace/Crystal/funa/README.md
create /home/user/workspace/Crystal/funa/shard.yml
create /home/user/workspace/Crystal/funa/src/funa.cr
create /home/user/workspace/Crystal/funa/spec/spec_helper.cr
create /home/user/workspace/Crystal/funa/spec/funa_spec.cr
Initialized empty Git repository in /home/user/workspace/Crystal/funa/.git/
$
2.ビルド
ソースファイル src/funa.cr を編集したら、次のようにビルドを行う。shard.yml に記述した依存関係が間違っておらず、編集後のソースが正しければ次のように表示されるはずである。
$ shards build funa
Resolving dependencies
Fetching https://github.com/crystal-lang/crystal-sqlite3.git
Fetching https://github.com/crystal-lang/crystal-db.git
Installing db (0.12.0)
Installing sqlite3 (0.20.0)
Writing shard.lock
Building: funa
$
2 回目からは sqlite3 パッケージのダウンロードが終わっているため次のようになる。
$ shards build funa
Dependencies are satisfied
Building: funa
$
参考までに funa.cr の内容を次に示す。
require "db"
require "sqlite3"
DB.open("sqlite3://:memory:") do |db|
db.close()
end
Crystal: 文字列リテラル
複数行文字列
文字列リテラル “….” には改行が含まれてもよい。
# 文字列リテラルは複数行に渡ってもよい。
str1 = "Hello
World!"
puts str1
これを実行すると、下のように表示される。
Hello
World!
値の埋め込み
“…..” の中の #{var} は特別に扱われ、変数 var の値がその文字列に埋め込まれる。
i = 1200
str2 = "i = #{i}"
puts str2
これを実行すると、下のように表示される。
i = 1200
パーセント文字列リテラル
% 文字列リテラルは %[….] などの形式の文字列で、この形式の文字列は二重引用符 ” はそのまま文字として使える。%[….] のカッコはカギカッコでなくてもよい。つまり、文字列内にカッコが含まれる場合、別のカッコを使用できる。また % の代わりに %q でも同じである。
puts %(hello ("world")) # => "hello (\"world\")"
puts %[hello ["world"]] # => "hello [\"world\"]"
puts %{hello {"world"}} # => "hello {\"world\"}"
puts %<hello <"world">> # => "hello <\"world\">"
puts %|hello "world"| # => "hello \"world\""
パーセント文字列配列リテラル
%w 文字列配列リテラルを使うと、文字列配列を見やすく定義できる。
puts %w(foo bar baz) # => ["foo", "bar", "baz"]
puts %w(foo\nbar baz) # => ["foo\\nbar", "baz"]
puts %w(foo(bar) baz) # => ["foo(bar)", "baz"]
バックスラッシュで複数文字列を結合する
複数の文字列リテラルをバックスラッシュで結合できる。
str2 = "<html>\n" \
" <head><title>Title</title></head>\n" \
" <body></body>\n" \
"</html>\n"
puts str2
これを実行すると、下のように表示される。
<html>
<head><title>Title</title></head>
<body></body>
</html>
ヒアドキュメント
Ruby や Perl 風のヒアドキュメントも書くことができる。これは <<-SYMBOL で始まり、SYMBOL で終わる文字列リテラルで、バックスラッシュや改行など任意の文字がそのまま使用できる。
heredoc = <<-STRING
<html>
<head><title>heredoc</title></head>
<body>
<h1>HereDoc</h1>
</body>
</html>
STRING
puts heredoc
これを実行すると、下のように表示される。
<html>
<head><title>heredoc</title></head>
<body>
<h1>HereDoc</h1>
</body>
</html>
Crystal: SQLite3 の使い方
Crystal の標準ライブラリではデータベースはサポートされていない。
そのため、shards コマンドを使って github から SQLite3 モジュールをインポートする必要がある。
その具体的な方法は別の投稿である「Shards とは何か?」に記載してある。
その内容でインポートする部分は shart.yml の中の次のところである。
dependencies:
sqlite3:
github: crystal-lang/crystal-sqlite3
shards コマンドを使ってこのプロジェクトをビルド (shards build) すると、この YAML ファイルを読み込んで指定されたリポジトリから必要なモジュールをダウンロードしてプロジェクト内のディレクトリにセットアップしてくれる。
shards コマンドは内部で crystal build コマンドを起動してターゲットソースをコンパイルして実行ファイルが作成される。
$ shards build
Dependencies are satisfied
Building: funa
$
以下に、そのときの SQLite3 を使ったサンプルプログラムを示す。sqlite3://%3Amemory%3A はメモリ上にデータベースを作成することを意味する。%3A は : のアスキーコードである。(URL内では : は特別な文字であるため)
require "db"
require "sqlite3"
# メモリ上のデータベースに接続する。
DB.open("sqlite3://%3Amemory%3A") do |db|
# テーブルを作成する。
db.exec "CREATE TABLE members (id integer not null primary key, name text not null, age integer not null, info text)"
# テーブルの3つのデータを挿入する。
db.exec "INSERT INTO members VALUES(?, ?, ?, ?)", 1, "Abe", 15, "080-2586-8756"
db.exec "INSERT INTO members VALUES(?, ?, ?, ?)", 2, "Aizawa", 15, "-"
db.exec "INSERT INTO members VALUES(?, ?, ?, ?)", 3, "Aoki", 14, "080-3310-1721"
# データの挿入が正しく行えたかクエリーを行って確認する。
db.query "SELECT * FROM members" do |rs|
# カラム名を表示する。
puts "#{rs.column_name(0)} #{rs.column_name(1)} #{rs.column_name(2)} #{rs.column_name(3)}"
# データを表示する。
rs.each do
puts "#{rs.read(Int32)} #{rs.read(String)} #{rs.read(Int32)} #{rs.read(String)}"
end
end
# 接続を閉じる。
db.close()
end
これを実行すると次のようになる。
$ ./bin/funa
id name age info
1 Abe 15 080-2586-8756
2 Aizawa 15 -
3 Aoki 14 080-3310-172
$
Crystal: MySQL の使い方
MySQL も他のデータベース同様、使用するためには shard コマンドでライブラリをプロジェクトにインストールする必要がある。
まず、crysla init コマンドを使ってプロジェクトを作成する。この例では koi という名前である。
$ crystal init app koi
プロジェクトが作成されたら shard.yml を開いて次の内容を targets ブロックの後に追加する。
dependencies:
mysql:
github: crystal-lang/crystal-mysql
プロジェクトフォルダ内で shard install コマンドを実行して crystal-mysql をそのプロジェクトにインストールする。
Crystal ソースファイル src/koi.cr を編集する。
次の例は test というデータベース上の products というテーブルにクエリーを行い、内容を表示するものである。
DB.open で指定している接続文字列の形式は次のようになっている必要がある。ただし、データベースは後からも指定できる。
mysql://ユーザ:パスワード@ホスト/データベース
# koi.cr https://crystal-lang.org/reference/1.9/database/index.html
require "db"
require "mysql"
module Koi
extend self
VERSION = "0.1.0"
SELECT = "SELECT * FROM products"
# MySQL Test
def mysqltest()
# 接続文字列を使って DB を開く。
db = DB.open "mysql://user:password@localhost/test"
begin
# SELECT クエリーを行う
db.query SELECT do |rs|
# カラム名を表示する。
puts "#{rs.column_name(0)} #{rs.column_name(1)} #{rs.column_name(2)} #{rs.column_name(3)}"
# クエリー結果を表示する。
rs.each do
id = rs.read(Int32)
product = rs.read(String)
amount = rs.read(Int32)
price = rs.read(Float64)
puts "#{id}, #{product}, #{amount}, #{price}"
end
end
ensure
db.close # 接続を閉じる。
end
end
end
モジュール関数 mysqltest を呼び出す。
Koi.mysqltest()
この例では、SELECT 文しか使っていないが、INSERT などを使う場合は、exec メソッドを使う。
(例) db.exec “INSERT INTO members VALUES(?, ?, ?, ?)”, 1, “Abe”, 15, “080-2586-8756”
このサンプルプログラムの実行例を下に示す。
$ ./koi
id product amount price
1, orange juice, 10, 120.0
2, apple juice, 10, 120.0
3, grape juice, 10, 120.0
4, soda, 15, 100.0
5, cola, 20, 110.0
6, Dr.Pepper, 0, 0.0
$
Crystal: 独自ライブラリを作って GitHub でホストする
独自ライブラリを作って github でホストすれば、どこでもそれを利用できる。
次のサンプルは HTML を生成するライブラリで名前は Kingyo である。
まず、crystal コマンドを使ってプロジェクトを作成する。
$ crystal init lib kingyo
README.md を更新する。例えば、次のように
## kingyo
Kingyo is the library to generate HTML.
### Installation
1. Add the dependency to your `shard.yml`:
```yaml
dependencies:
kingyo:
github: your-github-user/kingyo
- Run
shards install
Usage
require "kingyo"
TODO: Write usage instructions here
### Development
TODO: Write development instructions here
### Contributing
1. Fork it (<https://github.com/your-github-user/kingyo/fork>)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request
### Contributors
- [your-name-here](https://github.com/your-github-user) - creator and maintainer
次のコードは src/kingyo.cr の内容である。
# TODO: Write documentation for `Kingyo`
module Kingyo
extend self
VERSION = "0.1.1"
# html
def html(head, body, lang="en")
result = "<!DOCTYPE html>\n"
result += "<html lang=\"#{lang}\">\n"
result += head
result += body
result += "</html>\n"
end
# body
def body(content, css_class="")
result = "<body>\n"
if css_class != ""
result = "<body class=\"#{css_class}\">\n"
end
result += " #{content}\n</body>\n"
end
# head
def head(title, charset="utf-8", content="")
result = "<head>\n"
result += " <meta charset~\"#{charset}}\" />\n"
result += " <title>#{title}</title>\n"
if content != ""
result += " #{content}\n"
end
result += "</head>\n"
end
end
このプロジェクトは依存するパッケージはないし、ビルドもしないので shard.yml は特に変更する必要はない。(バージョンと作者くらいは変更しておいたほうが良い)
準備ができたら GitHub にリポジトリ kingyo を作ってコミットした内容をアップロードする。
これでこの kingyo パッケージがいつでも利用できる。試しに test_kingyo という app プロジェクトを作ってこの kingyo パッケージが使えるか確認する。
$ crystal init app test_kingyo
ソース src/test_kingyo.cr を次のように更新する。
# TODO: Write documentation for `TestKingyo`
require "kingyo"
VERSION = "1.0.0"
def main()
puts "TestKingyo.VERSION = #{VERSION}"
head = Kingyo.head(title="test_kingyo")
body = Kingyo.body("<h1>test_kingyo</h1>")
html = Kingyo.html(head, body)
puts html
end
main()
shard.yml に依存情報を追加する。
name: test_kingyo
version: 0.1.1
authors:
- makandat
targets:
test_kingyo:
main: src/test_kingyo.cr
dependencies:
kingyo:
github: makandat/kingyo
crystal: 1.9.2
license: MIT
プロジェクトフォルダで shards コマンドにより kingyo をビルドする。
$ shards build
GitHub から kingyo パッケージがインストールされて実行可能ファイルが bin/test_kingyo として作られるはずである。
そして bin/test_kingyo を実行すると次のように表示される。
$ bin/test_kingyo
TestKingyo.VERSION = 1.0.0
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset~"utf-8}" />
<title>test_kingyo</title>
</head>
<body>
<h1>test_kingyo</h1>
</body>
</html>
$
Crystal: ファイルやディレクトリの操作
ファイル操作は File クラスまたは FileUtils モジュールのメソッドを使って行うことができる。一方、ディレクトリの操作は Dir クラスまたは FileUtils モジュールのメソッドを使って行うことができる。
FileUtils モジュールのメソッドは File または Dir クラスのメソッドの別名であり、Unix (Linux) のファイル操作コマンドと同じような使い方で操作できる。
File と Dir のメソッドを使う場合、require は不要だが、FileUtils のメソッドを使うときは require “FileUtils” が必要である。
次に、ファイルやディレクトリ操作の簡単な例を示す。
# ファイル操作
require "file_utils"
FileUtils.cd("/home/user/bin") # ディレクトリの移動
p! FileUtils.pwd # 現在のディレクトリを表示
TEMPDIR1 = "/home/user/temp/dir1"
TEMPDIR2 = "/home/user/temp/dir2"
FileUtils.mkdir(TEMPDIR1) # ディレクトリの作成
p! Dir.exists?(TEMPDIR1) # ディレクトリが作成できたか確認
FileUtils.mv(TEMPDIR1, TEMPDIR2) # ディレクトリを移動
p! Dir.exists?(TEMPDIR2) # ディレクトリが移動できたか確認
FileUtils.rmdir(TEMPDIR2) # ディレクトリを削除
p! Dir.exists?(TEMPDIR2) # ディレクトリが削除できたか確認
FileUtils.cp("/home/user/.profile", "/home/user/temp/profile") # ファイルのコピー
p! File.exists?("/home/user/temp/profile") # ファイルのコピーができたか確認
File.delete("/home/user/temp/profile") # ファイルを削除
p! File.exists?("/home/user/temp/profile") # ファイルの削除ができたか確認
この実行例を下に示す。
$ ./bin/file_opr
FileUtils.pwd # => "/home/user/bin"
Dir.exists?(TEMPDIR1) # => true
Dir.exists?(TEMPDIR2) # => true
Dir.exists?(TEMPDIR2) # => false
File.exists?("/home/user/temp/profile") # => true
File.exists?("/home/user/temp/profile") # => false
Crystal: ファイル入出力
ファイルの入出力は File クラスのメソッドによって行うことができる。
もっとも簡単なのは、文字列全体のファイルへの書き込みと読み出しで write(), read() メソッドだけで行うことができる。下にその例を示す。
# file 入出力
# コマンドパラメータでファイルパスを得る。
if ARGV.size() == 0
STDERR.puts("No arguments")
exit 1
end
filepath = ARGV[0]
hello = "Hello
World!
"
begin
# ファイルに文字列を書く。
File.write(filepath, hello)
puts "Write OK"
# ファイル内容をすべて読み込む。
s = File.read(filepath)
puts s
rescue e
puts e.message
end
puts ".. Done."
また、行単位での読み込みを行う際には、read_line() メソッドや read_lines() メソッドを使う。下にその例を示す。
# ファイル入出力 行ごと
# コマンドパラメータでファイルパスを得る。
if ARGV.size() == 0
STDERR.puts("No arguments")
exit 1
end
filepath = ARGV[0]
# 行ごとにファイルを読む。
begin
File.each_line(filepath) do |line|
puts line
end
puts ".. each_line OK"
rescue e
puts e.message
end
# 行ごとにファイルを読んで配列に格納する。
begin
strarray = File.read_lines(filepath)
strarray.each do |line|
puts line
end
puts ".. read_lines OK"
rescue e
puts e.message
end
Crystal: ディレクトリの内容を取得する
ディレクトリの内容を読み取るには Dir クラスの glob() や entries() メソッドを使う。glob はクラスメソッドであり、entries はインスタンスメソッドである。
まず、 glob の使用例を示す。この例は単純にすべてのディレクトリの内容を読み取るものであるが、glob には読み取りに関し詳細な設定を行えるオーバーロードメソッドもある。
files = Dir.glob("/home/user/*")
files.each do |f|
puts f
end
entries メソッドはインスタンスメソッドなので Dir.new(path) によりインスタンスを得る必要がある。
下の例はもっとも簡単な使い方であるが、entries は glob のようにオプションを様々に設定できる機能はない。
# ディレクトリの内容一覧
if ARGV.size() == 0
STDERR.puts("No arguments")
exit 1
end
path = ARGV[0]
if !Dir.exists?(path)
STDERR.puts("#{path} does not exists.")
exit 1
end
list = Dir.new(path).entries()
list.each do |x|
puts x
end
Crystal: ファイルパスの操作
ファイルパスの操作には、File クラスのメソッドと Path 構造体のメソッドを使う方法がある。
下にその簡単な使用例を示す。
# ファイルパス
filepath = "/home/user/temp/hello.txt"
puts File.basename(filepath) # ファイル名のみ
puts File.dirname(filepath) # ディレクトリ部分
puts File.extname(filepath) # 拡張子 (ドットを含む)
p! File.join(["~/bin", "build.sh"]) # ファイルのパスを構成する。
p! File.expand_path("path.cr") # 絶対パスを返す。
# Path 構造体を使う例
p! Path[filepath].basename # ファイル名のみ
p! Path[filepath].dirname # ディレクトリ部分
p! Path[filepath].extension # 拡張子 (ドットを含む)
p! Path[filepath].parent # 親のディレクトリ名 (Path のインスタンス)
# インスタンス化しても使用できる。
path = Path.new(filepath)
# パスの部分に分ける。
path.each_part do |x|
puts x
end
このサンプルの使用例を下に示す。
$ ./bin/path
hello.txt
/home/user/temp
.txt
File.join(["~/bin", "build.sh"]) # => "~/bin/build.sh"
File.expand_path("path.cr") # => "/home/user/workspace/Crystal/path.cr"
Path[filepath].basename # => "hello.txt"
Path[filepath].dirname # => "/home/user/temp"
Path[filepath].extension # => ".txt"
Path[filepath].parent # => Path["/home/user/temp"]
/
home
user
temp
hello.txt
Crystal: 日付時刻
Crystal では日付時刻の処理は Time 構造体のメソッドによって行うことができる。
次に Time の簡単な使用例を示す。
# 日付時刻 https://crystal-lang.org/api/1.9.2/Time.html
# 現在の時刻
p! Time.utc
p! Time.local
# 時刻値を作る。
t1 = Time.local(2020, 2, 22, 20, 20, 20)
p! t1
# 時刻の部分を得る。
p! t1.year
p! t1.second
# 文字列との変換
now = Time.local
s = now.to_s("%Y-%m-%d %H:%M:%S") # 文字列にする。
puts s
JST = Time::Location.load("Asia/Tokyo")
t2 = Time.parse(s, "%Y-%m-%d %H:%M:%S", JST) # 文字列から時刻にする。
p! t2
# 時間間隔の計算
span = Time.utc(2020, 2, 28) - Time.utc(2020, 1, 1)
p! span.days # 元旦から2月28日までの日数
p! Time.utc(2020, 1, 1) + Time::Span.new(days:5) # 5日後
# 時間計測
t3 = Time.monotonic # 現在の CPU 時間
sleep 1 # 1秒
t4 = Time.monotonic
puts (t4 - t3).to_s # 約 1 秒
このコードの実行例を下に示す。
$ ./bin/time
Time.utc # => 2023-09-19 23:31:31.564272781 UTC
Time.local # => 2023-09-19 23:31:31.565349765 +00:00
t1 # => 2020-02-22 20:20:20.0 +00:00
t1.year # => 2020
t1.second # => 20
2023-09-19 23:31:31
t2 # => 2023-09-19 23:31:31.0 +09:00 Asia/Tokyo
span.days # => 58
(Time.utc(2020, 1, 1)) + Time::Span.new(days: 5) # => 2020-01-06 00:00:00.0 UTC
00:00:01.019929696
$
Crystal: 正規表現
Crystal では Regex クラスのメソッドを使って正規表現処理を行うことができる。
このクラスでは、=~ や $~ のような Perl 風の演算子や特殊変数も使用できる。
下に Regex クラスの簡単な使用例を示す。
# 正規表現 Regex https://crystal-lang.org/api/1.9.2/Regex.html
str = "無職転生S2"
p /転生/ =~ str # 見つかった位置を表示 (バイト位置でなく文字列の位置)
if match_data = /S\d/.match(str)
p match_data.string # 検索した文字列
p match_data[0] # 見つかった文字列
p! $~ # Perl 風の特殊変数も使える。
end
/\d/.match_at_byte_index("F15J F35B", 3) # 数字を 3 バイト目から検索する。
p! $~[0] # 見つかったバイト位置
このコードの実行例を下に示す。
$ ./bin/regex
2
"無職転生S2"
"S2"
$~ # => Regex::MatchData("S2")
$~[0] # => "3"
$
Crystal: JSON
Crystal では JSON モジュールのメソッドを使うと、JSON を簡単に扱うことができる。
ただし、このモジュールを使うときは require “json” が必要である。
下に JSON モジュールの簡単な使用例を示す。
# JSON https://crystal-lang.org/api/1.9.2/JSON.html
require "json"
json_text = %([1, 2, 3]) # %文字列でデータを定義
a = Array(Int32).from_json(json_text) # JSON を配列に変換
json_text = %({"x": 1, "y": 2}) # %文字列でデータを定義
h = Hash(String, Int32).from_json(json_text) # JSON を連想配列に変換
# 逆変換
puts a.to_json
puts h.to_json
# JSON を作成する。
string = JSON.build do |json|
json.object do
json.field "name", "foo"
json.field "values" do
json.array do
json.number 1
json.number 2
json.number 3
end
end
end
end
puts string
このコードの実行例を下に示す。
$ ./bin/json
[1,2,3]
{"x":1,"y":2}
{"name":"foo","values":[1,2,3]}
$
Crystal: 環境変数
Crystal では ENV モジュールの ENV という連想配列 (ハッシュテーブル、辞書) を使うと簡単に環境変数にアクセスできる。
下に ENV の簡単な使用例を示す。
# 環境変数 ENV https://crystal-lang.org/api/1.9.2/ENV.html
# 環境変数一覧を表示する。
ENV.each do |kv|
puts "#{kv[0]}:#{kv[1]}"
end
# 環境変数 PYTHONPATH の値を表示する。
puts ENV["PYTHONPATH"]
このコードの使用例を下に示す。
$ ./bin/env
SHELL:/bin/bash
PWD:/home/user/workspace/Crystal
LOGNAME:user
XDG_SESSION_TYPE:tty
HOME:/home/user
.........
.........
.:/home/user/lib/Python
Crystal: Fiber とは何か?
Crystal の Fiber とは、簡単に言えば軽量なスレッドのようなものです。
ただし、使用できるメモリが 8MB だけ (v1.9.2 の場合) しかないので重い処理には向きません。
Fiber との通信には Channel という軽量なメカニズムを使います。Channel により複雑な同期処理などが簡単にできてスレッドより使いやすいです。
次に Fiber の簡単な使用例を示します。この例では、2つの Fiber を起動し、2つが終了するまで待ってからメインプロセスを終了させます。
Fiber が終わったかどうかは Channel から送られてくる信号で確認しています。
もし、この処理がないと、メインプロセスが Fiber を起動した後、すぐに終了してしまい何も表示されません。
# Fiber: 軽量なスレッドのようなもの https://crystal-lang.org/reference/1.9/guides/concurrency.html#fibers
channel1 = Channel(Int32).new
channel2 = Channel(Int32).new
# Fiber1 を開始する。
spawn do
sleep 1.second
puts "fiber1"
channel1.send(1)
end
# Fiber2 を開始する。
spawn do
sleep 2.second
puts "fiber2"
channel2.send(2)
end
# Channel の値を受け取るまでメインの実行はブロッキングされる。
puts "The fibers are blocking .."
p channel1.receive
p channel2.receive
puts " .. Done."
このコードの実行例を下に示します。
$ ./bin/fiber
The fibers are blocking ..
fiber1
1
fiber2
2
.. Done.
$
Mutex を使いリソースの競合を回避
Crystal にも Mutex があって、共有リソースを複数のファイバーどうしが同時に使用できないようにすることができる。
次の例では1つのファイバーとメインが標準出力へ文字列を同時に出力できないようにしている。
# Mutex https://crystal-lang.org/api/1.9.2/Mutex.html
mutex = Mutex.new
puts "Start .."
# 直ちに fiber が開始される。
spawn do
mutex.lock # stdout をロック
sleep 2.second
puts "filber 1 done."
mutex.unlock
end
sleep 1.second # fiber が動作する前に終わらないようにする。
mutex.lock
puts "main done."
mutex.unlock
この実行例を示す。
$ ./bin/mutex
Start ..
filber 1 done.
main done.
Crystal: タプル
タプルは配列に似ているが、固定長、書き換え不可、スタック上に割り当てられるという特徴がある。
また、メンバーは同じで型でなくてもよい。
タプルには普通のタプル (Tuple) と名前付きタプル (NamedTuple) がある。
タプルのメンバーは配列のようにインデックスで指定するが、名前付きタプルはキーで指定する。キーは文字列またはシンボルのどちらかである。
次にタプルの簡単な使用例を示す。
# Tuple
# https://crystal-lang.org/api/1.9.2/Tuple.html
# https://crystal-lang.org/api/1.9.2/NamedTuple.html,
# タプルを返す関数
def one_and_hello
{1, "hello"}
end
# タプル
tuple = {1, "hello", 'x'} # Tuple(Int32, String, Char)
p! tuple[2] # 要素の取得
# イテレータを使う
tuple.each do |x|
p! x
end
# タプルを返す関数を使う。
one, hello = one_and_hello
# 名前付きタプル
language = {name: "Crystal", year: 2011} # NamedTuple(name: String, year: Int32)
p! language[:name] # => "Crystal"
p! language[:year] # => 2011
p! language["year"] # キーには文字列も使える。
# language[:other] # 存在しないキーを使うとコンパイル時にエラーとなる。
p! language["other"]? # ? を付けると存在しないキーを使うと nil を返す。
p! language.fetch(:other, 0) # fetch を使うと存在しないキーを指定してもエラーにならずデフォルト値を返す。
p! language.has_key?(:other) # キーが存在するかチェックする。
p! language.keys # キー一覧を得る。
p! language.sorted_keys # ソートされたキー一覧を得る。
p! language.size # メンバーの数を得る。
下にこの実行例を示す。
$ ./bin/tuple
tuple[2] # => 'x'
x # => 1
x # => "hello"
x # => 'x'
language[:name] # => "Crystal"
language[:year] # => 2011
language["year"] # => 2011
language["other"]? # => nil
language.fetch(:other, 0) # => 0
language.has_key?(:other) # => false
language.keys # => {:name, :year}
language.sorted_keys # => {:name, :year}
language.size # => 2
Crystal: 列挙型 (Enum)
列挙型 (Enum) は case 文などで番号 (数) の代わりに使うもので、コードを読みやすくするためによく用いられる。
次に列挙型の使用例を示す。
# 列挙型 https://crystal-lang.org/api/1.9.2/Enum.html
# 列挙型 Color の宣言
enum Color
Red # 0
Green # 1
Blue # 2
end
# 列挙型の値は整数
p! Color::Green.value
a = Color::Red # 代入
# 判別
case a
when Color::Red
puts "Red"
when Color::Green
puts "Green"
when Color::Blue
puts "Blue"
else
puts "Not defined."
end
この実行例を下に示す。
$ ./bin/enum
Color::Green.value # => 1
Red
$
Enum は単なる型ではなくて、abstruct struct であり、実体化したものが enum である。
Enum は struct なので型としてだけでなく、メソッドなどを持つ。
Crystal: 連想配列 (Hash)
連想配列は場合によって「辞書」「ハッシュテーブル」「マップ」などとも呼ばれる。
Crystal では Hash クラスとして実装されている。
使い方は他の言語と基本的に同じであるが、インスタンス化するときにキーと値の型を指定する必要がある。
ハッシュのリテラルは Ruby と同じように key => value を使う。
(例){"A" => 10, "B" => 20}
次に連想配列の使用例を示す。
# Hash https://crystal-lang.org/api/1.9.2/Hash.html
# 初期化
lepin = Hash(String, Int32).new
lepin["Lepin"] = 32
lepin["Jigen"] = 40
lepin["Goemon"] = 28
lepin["Zenigata"] = 45
lepin["Fujiko"] = 27
# キー一覧
p! lepin.keys
# メンバー一覧を表示
lepin.each do |t| # t はタプル
p t[0], t[1]
end
# キーの有無をチェックする
p! lepin.has_key?("Goemon")
p! lepin.has_key?("Ishikawa")
# 値の変更とその値の確認
lepin["Lepin"] = 31
p! lepin["Lepin"]
この実行例を下に示す。
$ ./bin/hash
lepin.keys # => ["Lepin", "Jigen", "Goemon", "Zenigata", "Fujiko"]
"Lepin"
32
"Jigen"
40
"Goemon"
28
"Zenigata"
45
"Fujiko"
27
lepin.has_key?("Goemon") # => true
lepin.has_key?("Ishikawa") # => false
lepin["Lepin"] # => 31
$
Crystal: 集合 (Set)
集合は Set 構造体によって実装されている。
集合は配列と違って、同じ要素は1つだけ格納できる。例えば整数の集合の場合、100 というメンバーがあった場合、後から 100 を追加しても無視される。
次に集合の簡単な使用例を示す。
# Set https://crystal-lang.org/api/1.9.2/Set.html
# 初期化
s1 = Set{1, 2, 3}
s2 = [1, 2, 3].to_set
s3 = Set.new [-1, -2, -3]
# == 演算子
p! s1 == s2
p! s2 == s3
# 要素の追加
s1.add(4)
p! s1
s1 << 5 # add(x) と同じ
p! s1
s3.concat [-4, -5] # 結合
p! s3
# 要素が含まれているか?
p! s2.includes?(3)
p! s2.includes?(4)
# 空集合か?
p! s2.empty?
# 部分集合か?
p! s2.subset_of?(s1)
p! s1.subset_of?(s2)
# 要素の削除
p! s3.delete(-5)
p s3
# 変換
p! s1.to_s # 文字列化
p! s1.to_a # 配列化
この実行例を以下に示す。
$ ./bin/set
s1 == s2 # => true
s2 == s3 # => false
s1 # => Set{1, 2, 3, 4}
s1 # => Set{1, 2, 3, 4, 5}
s3 # => Set{-1, -2, -3, -4, -5}
s2.includes?(3) # => true
s2.includes?(4) # => false
s2.empty? # => false
s2.subset_of?(s1) # => true
s1.subset_of?(s2) # => false
s3.delete(-5) # => true
Set{-1, -2, -3, -4}
s1.to_s # => "Set{1, 2, 3, 4, 5}"
s1.to_a # => [1, 2, 3, 4, 5]
Crystal: クラス (Class) とモジュール (Module)
クラスは抽象クラスの Class クラスのインスタンスとして定義される。
Class は Object から派生した Value を継承している。よって、これらのクラスのメソッドも使用できる。
クラスの継承
次にクラスの使用例を示す。クラスの使い方は Ruby とだいたい同じだがサポートされていない機能もある。
# class https://crystal-lang.org/api/1.9.2/Class.html
module Koi
# コイ科クラス
class Koika
def initialize()
@name = ""
@size = 0
@bunpu = Array.new
end
def size
@size.to_s + "cm"
end
# 下のような書き方はエラーになる。
#attr_reader: name
def name
@name
end
def dist
@bunpu
end
end
# ニゴイクラス
class Nigoi < Koika
def initialize()
@name = "ニゴイ"
@size = 60
@bunpu = ["本州", "四国", "九州"]
end
# クラスメソッドの定義
def self.kanji()
"似鯉"
end
end
# マゴイクラス
class Magoi < Koika
def initialize()
@name = "マゴイ"
@size = 100
@bunpu = ["北海道", "本州", "四国", "九州"]
end
# クラスメソッドの定義
def self.kanji()
"真鯉"
end
end
end
これらのクラスの使用例を示す。
# test_class
require "./class.cr"
# モジュール Koi に含まれる Magoi クラスをインスタンス化
magoi = Koi::Magoi.new
# モジュール Koi に含まれる Nigoi クラスをインスタンス化
nigoi = Koi::Nigoi.new
# Nigoi クラスの基底クラス Koika のメソッドを使用する。
p nigoi.name
p nigoi.size
# Magoi クラスの基底クラス Koika のメソッドを使用する。
p magoi.dist
# Magoi クラスのクラスメソッドを使用する。
p Koi::Magoi.kanji
# すべてのクラスの基底クラス Object のメソッドを使用する。
p magoi.hash
この実行例を下に示す。
$ ./bin/test_class
"ニゴイ"
"60cm"
["北海道", "本州", "四国", "九州"]
"真鯉"
12321634533550542469
プロパティ
Ruby ではプロパティを定義するのに attr_accessor などを使うが、Crystal では property などを使う。
- property
- getter
- setter
クラスメソッド
クラスメソッドの定義には self. を使用する。
def self.do_this
...
end
Ruby ではクラスメソッドのコール時に :: を使うが、Crystal では . を使う。
(Ruby) Class1::do_this => (Crystal) Class1.do_this
モジュールの include
クラスは他のクラスの継承だけでなく、モジュールを include して機能を拡張できる。次の例は Crystal のドキュメントに出ているサンプルである。
この例ではモジュールに含まれるメソッドを単純にクラスに include している。
module ItemsSize
def size
items.size
end
end
class Items
include ItemsSize
def items
[1, 2, 3]
end
end
items = Items.new
items.size # => 3
モジュールの extend
クラスにモジュールを include するのに似ている extend というのもある。include が単純にモジュールの内容をクラスにインクルードするのに対し、extend はクラスメンバーとして機能拡張するというイメージである。
次の例は Crystal ドキュメントに出ているサンプルである。extend により size というメソッドは Items のクラスメソッドになる。
module SomeSize
def size
3
end
end
class Items
extend SomeSize
end
Items.size # => 3
モジュール自体のメソッドなどを公開する場合も extend が必要になる。その場合は、extend self とする。下にその例を示す。
module MyModule
extend self
def init
...
end
....
end
Generics
Crystal は静的型付け言語なので C# のような Generics の機能がある。
これは、クラスで使われる変数の型を指定できるものである。
次の例で、KVPair というクラスは key と value というプロパティを持つ。このクラスは Generic クラスなのでこれらのプロパティの型を制限できる。
# Generics
class KVPair(K, V)
def initialize(@key : K, @value : V)
end
def key
@key
end
def value
@value
end
end
a = KVPair(String, String).new("All", "全部")
p a.key, a.value
このコードの実行例を示す。
$ ./bin/generics
"All"
"全部"
$
Generics については他にも様々な機能がある。詳細は Crystal のドキュメントを参照のこと。
Crystal: 構造体とレコード (Struct, record)
Cystal の構造体 (Struct) は C の構造体とは違いクラスに近い。
そして、クラス同様に継承などもでき、Struct 自体も Value を継承している。なお、Value は Object を継承している。
次に Struct の使用例を示す。
# Struct https://crystal-lang.org/api/1.9.2/Struct.html
# Point 構造体の定義
struct Point
# コンストラクタでメンバー変数を初期化する必要がある。
def initialize(@x : Int32, @y : Int32)
end
# アクセサメソッド x
def x
@x
end
# アクセサメソッド y
def y
@y
end
end
# インスタンス化
p1 = Point.new 1, 2
p2 = Point.new 1, 2
p3 = Point.new 3, 4
# アクセサメソッドを定義しないとこういう書き方はできない。
p p1.x
p p1.y
# 演算子メソッド ==
p p1 == p2 # => true
p p1 == p3 # => false
# Object を継承しているので Object クラスのメソッド hash が使える。
p p3.hash
この実行例を下に示す。
$ ./bin/struct
1
2
true
false
12286184678607581374
record マクロは Struct を使いやすくしたもので、Struct をベースにしている。
# record マクロは Struct をベースにしている。
record Point, x : Int32, y : Int32
p = Point.new 1, 2 # => #<Point(@x=1, @y=2)>
p! p.x # => 1
p! p.y # => 2
この実行例を示す。
p.x # => 1
p.y # => 2
Crystal: ログを取るには
古いバージョンの Crystal には Logger というパッケージがあって、ググるとそのサンプルがよく出てくる。
しかし、2023 年時点ではバージョンが 1.9.2 になっており、その標準ライブラリには Log と言うパッケージに変更されており、以前の Logger とは互換性がないようである。
これについては、ググってもあまり記事がヒットしない。
Crystal のドキュメントには次のようなコードが最初の方に出てくる。
ここで、Log.info のパラメータがブロックになっていることに注意。
つまり、ブロックで出力内容に何らかの加工ができるということを意味する。
require "log"
Log.info { "Program started" }
これを実行すると、コンソール画面にこの文字列が表示される。つまり、デフォルトではログの出力先が標準出力になっていることを意味する。
とりあえず、デバッグのためにはファイルへログを出力したいので、標準出力から指定したファイルにログの出力先を変えたい。
これをするには、Backend で出力先を作り、それをログにバインドするとよいらしい。
具体的には次のようなちょっと面倒なコードが必要になった。
ここでログ出力メソッド (例えば Log.error) のパラメータは文字列でなくブロックであることに注意。このブロック内で処理が可能で、最終的に文字列を作るというシナリオのようである。
::Log.setup のブロックでは、この例では1つのみだが、ソース名や出力先を変えたりして複数のログを定義できる。
# Log https://crystal-lang.org/api/1.9.2/Log.html
require "log"
# ログの出力先を "log.txt" というファイルにする。
backend = ::Log::IOBackend.new(File.new("./log.txt", "w"))
# ログの出力先 (backend) をバインドする。
::Log.setup do |c|
c.bind("*", :debug, backend) # 任意のログソース "*" に対してバインドを行う。
end
::Log.for("*") # 任意のログソース "*" に対するコンストラクタ
# デフォルトのログソース Log に対してログ操作を行う。
Log.debug {"Log start .." }
Log.info {"Log info"}
Log.error{"Log error"}
Log.warn {"Log warn"}
Log.fatal {"Log fatal"}
Log.debug {" .. Done."}
# ./bin/log;cat ./log.txt;rm ./log.txt
このコードの実行例を示す。
$ ./bin/log;cat ./log.txt;rm ./log.txt
2023-09-21T04:37:43.969739Z DEBUG - Log start ..
2023-09-21T04:37:43.969742Z INFO - Log info
2023-09-21T04:37:43.969742Z ERROR - Log error
2023-09-21T04:37:43.969743Z WARN - Log warn
2023-09-21T04:37:43.969743Z FATAL - Log fatal
2023-09-21T04:37:43.969744Z DEBUG - .. Done
$
Crystal: コマンドを実行するには
コマンドを実行するには Process クラスを使う。
このモジュールには、多くのメソッドがあるが単純にコマンドを実行するだけなら run メソッドが便利である。
def self.run(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, input : Stdio = Redirect::Close, output : Stdio = Redirect::Close, error : Stdio = Redirect::Close, chdir : Path | String | Nil = nil) : Process::Status
このメソッドにはブロック付きのバージョンもあり次のようになっている。
def self.run(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, input : Stdio = Redirect::Pipe, output : Stdio = Redirect::Pipe, error : Stdio = Redirect::Pipe, chdir : Path | String | Nil = nil, &)
このメソッドのうち、command にコマンド文字列を指定する。args はパラメータのタプルで空白で区切られた文字列単位で指定する。
あと、注意しないこととして output パラメータを STDOUT に設定しないと、コマンドの出力は標準出力に表示されない。また、デフォルトで shell パラメータが false になっているので、シェル経由でコマンドを実行する場合は、shell:true にする。
次に簡単な例を示す。
# クラスメソッド run でコマンドを実行する。
sts = Process.run("python3", {"-V"}, output:STDOUT) # python3 のバージョンを表示。python3 コマンドはシェルスクリプトでないので shell:true にしなくてよい。
p! sts.exit_status
# Ubuntu のバージョンを表示するシェルスクリプトを実行する。
Process.run("~/bin/ver", shell:true, output:STDOUT) # シェル機能 (ここでは ~) を使う場合は shell:true にしないとエラーになる。
# ブロック付きバージョンの run
Process.run("perl", {"-v"}) { |pr|
puts "pid = #{pr.pid} OK"
}
これを実行すると、次のようになる。
Python 3.11.4
sts.exit_status # => 0
Distributor ID: Ubuntu
Description: Ubuntu 23.04
Release: 23.04
Codename: lunar
pid = 18601 OK
Process クラスをインスタンス化してコマンドを実行することもできる。この場合、インスタンス化が成功すると、ただちにコマンドの実行が開始される。
コマンドが終了するまで待つには、wait メソッドを使う。これがないと次の例ではメインプログラムがコマンドの終了を待たずに終了してしまう。
# プロセスオブジェクトを作成する。(そして直ちに実行される)
p1 = Process.new("ls", {"-l", "/"}, shell:true, output:STDOUT) # ルートディレクトリ / の内容一覧を表示
p1.wait # プロセスが終わるまで待つ。
コマンドが単純に実行されすぐ終わるものでなく、プロセス間通信などを行う場合は、こちらの方法がよい。プロセスオブジェクトにはいろいろなメソッドがあるので様々な操作ができる。
次にこの実行例を示す。
$ ./bin/process
合計 2097232
lrwxrwxrwx 1 root root 7 5月 13 2022 bin -> usr/bin
drwxr-xr-x 4 root root 4096 9月 21 13:57 boot
drwxr-xr-x 2 root root 4096 5月 13 2022 cdrom
drwxr-xr-x 19 root root 4840 9月 21 12:43 dev
drwxr-xr-x 141 root root 12288 9月 21 13:56 etc
drwxr-xr-x 4 root root 4096 7月 24 11:46 home
Crystal で C の関数を使うには
Crystal で C の関数を使うのは簡単である。
LibC という Crystal のモジュールは Crystal::System 名前空間に統合されているので、glib の関数をそのまま使える。
glibc の関数がそのまま使えれば、それらの関数を直接呼び出す Crystal 関数を作れば、C で関数を作るのと同じことである。
ただ、残念なことにすべての glibc 関数が使えるわけではない (サンプルのため) ようである。しかし、テンプレートに「自分でfunctionを記述することもできる」らしい。
Qiita: CrystalでC言語のバインディングを自動生成する crystal_lib
他の言語ではポインタを扱えないものもあるが、Crystal ではそれも可能である。
次の例は、C の関数 strlen() で文字列の長さを取得するものである。LibC は System に統合されているので require は不要である。
n = LibC.strlen("Hello, World!")
p n
その他の C ライブラリ関数を使う場合は、GitHub でホストされており shards コマンドでインストールできるものもあるが、一般的には独自に LibC 相当のものを作る必要がある。
詳しい解説は Specification: C bidings に出ている。(下記)
C bindings
lib
fun
struct
union
enum
Variables
Constants
type
alias
Callbacks
Unsafe code
LibC は glibc の一部の関数しか使えないので、その他の関数を使いたい場合は自分でライブラリを定義すればよい。
上の C bindings に方法が出ている。具体的なサンプルを下に示す。これは math ライブラリ (-lm) の関数を使う例である。
# libm
@[Link("m")]
lib LibM
fun cos(value: Float64) : Float64
fun sin(value: Float64) : Float64
fun tan(value: Float64) : Float64
end
fun radian(deg: Float64): Float64
deg / 180.0 * 3.1415926
end
p LibM.cos(radian(0.0))
p LibM.sin(radian(90.0))
p LibM.tan(radian(45.0))
これをビルドして実行すると下のようになる。
$ ./bin/libm
1.0
0.9999999999999997
0.9999999732051038
$
Void* 型のパラメータ
C の関数には void* 型のパラメータを渡す必要があるものがある。このようなパラメータは Box(T) クラス を使用することで実現できる。
Crystal: require について
require はファイルを読み込んで、プロジェクトに統合する。
もし、ファイル名の拡張子が .cr の場合は、拡張子は書かなくてよい。(通常は書かない)
ファイルの場所は、まず Crystal の標準ライブラリ、プロジェクト内の lib フォルダ、CRYSTAL_PATH 環境変数の内容である。カレントディレクトリにあるモジュールを読み込む場合は、”./” を先頭に付ける必要がある。
lib フォルダは通常、shards コマンドで外部のモジュールをインストール (shards install) すると自動的に作られる。
その際、外部モジュールの場所を、shards init で作られる shard.yml の “dependencies” セクションに記述しておく必要がある。
対象のファイルが、サブフォルダにある場合は “/” を使ってファイルシステムのツリー記法のように記述する。
複数のファイルをまとめて指定することもできる。その場合は、ディレクトリ名を指定する。その時のツリー構造は決まりがあり、一例として次のように書く。
- project
- src
- file
- sub1.cr
- sub2.cr
- file.cr (requires "./file/*")
- file
- src
詳細は Requiring files を参照のこと。
Crystal: HTTP の簡単な例
HTTP モジュールを使うと
- HTTP クライアント
- HTTP サーバ
- Web ソケット
を簡単に作ることができる。
このモジュールには次のようなサブモジュールがある。
Client
BodyType
Response
TLSContext
CompressHandler
Cookie
SameSite
Cookies
ErrorHandler
FormData
Builder
Error
FileMetadata
Parser
Part
Handler
HandlerProc
Headers
LogHandler
Params
Request
Server
ClientError
Context
RequestProcessor
Response
StaticFileHandler
DirectoryListing
Status
WebSocket
CloseCode
WebSocketHandler
次に簡単な HTTP サーバの例を示す。この例ではすべてのリクエストに対して “Hello world! The time is 現在の時刻” という文字列を返す。
# HTTP Server https://crystal-lang.org/api/1.9.2/HTTP/Server.html
require "http/server"
# HTTP サーバを作成しブロックを実行する。
server = HTTP::Server.new do |context|
# 応答を返す。
context.response.content_type = "text/plain"
context.response.print "Hello world! The time is #{Time.local}\n"
# リクエスト元を表示する。
if context.request.hostname.nil?
puts "> Nil"
else
puts "> #{context.request.hostname}"
end
end
# TCP Server を作成し、IP アドレスとポートをバインドする。
address = server.bind_tcp("127.0.0.1", 8080)
puts "Listening on http://#{address}"
# リクエストを待つ
server.listen
次に 簡単な HTTP クライアントの例を示す。
# http/client
require "http/client"
# localhost:8080 にリクエストを送り、応答を得る。
response = HTTP::Client.get "http://localhost:8080"
# 応答のステータスコードを表示
puts response.status_code.to_s
# 応答本体の先頭行を表示
puts response.body.lines.first
これらを実行する例を示す。
サーバ側
$ ./bin/http-server
Listening on http://127.0.0.1:8080
> localhost
> localhost
> localhost
クライアント側
$ ./bin/http-client
200
Hello world! The time is 2023-09-22 07:43:16 +00:00
$ ./bin/http-client
200
Hello world! The time is 2023-09-22 07:43:19 +00:00
$ ./bin/http-client
200
Hello world! The time is 2023-09-22 07:43:20 +00:00
$
Crystal: 型と関数
パラメータと関数値の型指定
Crystal は Ruby と違い、静的型付け言語である。そのため、関数定義においても型の指定ができる。
次の例で関数 distance は2つの Float64 型のパラメータを取り、関数値として Float64 を返す。
(注意) 型指定の : Float64 でコロンの前後には空白が必要である。
# def1.cr
def distance(x : Float64, y : Float64) : Float64
Math.sqrt(x * x + y * y)
end
p! distance(12.0, 4.0)
次の例のように型を指定せずに関数を定義することもできる。この場合は、型が自動変換されて計算される。
# def2.cr
def distance(x, y)
Math.sqrt(x * x + y * y)
end
p! distance(2_u8, 4_u16)
次の関数は2つの文字列を連結する。仮にパラメータとして整数を与えると文字列でないのでコンパイル時にエラーとなる。
# def3.cr
def concat(s1 : String, s2 : String) : String
s1 + s2
end
p concat("0", "1")
# p concat(0, 1) はコンパイルエラーになる。
ブロック
他の言語ではブロックと無名関数は別の意味であることが多いが、Crystal や Ruby ではまとめてブロックと呼ばれる。
そして、Crystal や Ruby では関数パラメータとしてブロックを渡すことができる。Ruby は動的型指定なのでブロックの型指定はできないが、Crystal の場合は型指定ができる。
次の例は32ビット整数のパラメータを受け取り、32ビット整数を返すブロックをパラメータを持つ関数の例である。
def int_to_int(&block : Int32 -> Int32)
block
end
proc = int_to_int { |x| x + 1 }
n = proc.call(1) # => 2
puts n.to_s
次の例はブロックの戻り値の型を指定しない例である。この例だと戻り値が32ビット整数と文字列を使っている。
def some_proc(&block : Int32 -> _)
block
end
proc = some_proc { |x| x + 1 }
y = proc.call(1) # 2
p y
proc = some_proc { |x| x.to_s }
y = proc.call(1) # "1"
p y
Proc
Proc は無名関数を表すオブジェクトである。無名関数は -> block の形式で作成する。下に、無名関数を変数に代入し、call メソッドで実行する例を示す。
# arrow.cr
itos = ->(x: Int32) { x.to_s }
p itos.call(100)
オーバーロード
関数名が同じでパラメータや戻り値の型が違う関数をオーバーロードという。
Crystal は型指定が可能なので、関数のオーバーロードが可能である。
次にオーバーロードの例を示す。
# This methods greets *recipient*.
def say_hello(recipient : String)
puts "Hello #{recipient}!"
end
# This method greets *times* times.
def say_hello(times : Int32)
puts "Hello " * times
end
say_hello "World"
say_hello 3
Crystal: ポインタ
Crystal ではポインタがサポートされているが、唯一の unsafe なオブジェクトとされている。
そのため、ポインタは積極的に使うものではない。
使う必要があるのは、C 関数のパラメータとしてぐらいだと思われる。
C のライブラリ関数にはパラメータとしてポインタを渡す必要があるので必ず必要になる。
ここでは、ポインタの簡単な使用例を下に示す。
# Pointer https://crystal-lang.org/api/1.9.2/Pointer.html
# 10バイトの0クリアされたメモリを確保する。
p0 = Pointer.malloc(10, 0_u8)
p1 = p0
p p1.value # ポインタ先頭の値
p1.value = 0x30 # ポインタ先頭の値を書き換え
p p1.value # 書き換えがうまくいったかチェック
# 続く4つの値を書き換え
p1 += 1
p1.value = 0x31
p1 += 1
p1.value = 0x32
p1 += 1
p1.value = 0x33
# バッファの内容を全部表示
puts "Contents .."
10.times do |i|
p1 = p0 + i
p p1.value
end
# メモリは自動的に解放される。
puts ".. Done."
このサンプルの実行例を下に示す。
$ ./bin/pointer
0
48
Contents ..
48
49
50
51
0
0
0
0
0
0
.. Done.
$
Crystal: CSV
CSV クラスは CSV データを扱うクラスである。このクラスを使うと、CSV データの扱いが非常に楽になる。
次のコードは Crystal のドキュメントに出ているサンプルにコメントを追加したものである。
# CSV
require "csv"
# コンストラクタ CSV.new(sio : String|IO, headers=false, strip=false, separator : Char = DEFAULT_SEPARATOR, quote_char : Char = DEFAULT_QUOTE_CHAR)
csv = CSV.new("Name, Age\nJohn, 20\nPeter, 30", headers: true)
csv.next # => true 次の行へ (最初の行はタイトル行)
# データの先頭行
csv["Name"] # => "John"
csv[0] # => "John"
csv[-2] # => "John"
csv[/name/i] # => "John"
csv["Age"] # => " 20"
csv.row.to_a # => ["John", " 20"] 配列に変換
csv.row.to_h # => {"Name" => "John", "Age" => " 20"} 連想配列に変換
# データ行の2行目
csv.next # => true
csv["Name"] # => "Peter"
csv.next # => false データ行は2行だけなので false になる。
Crystal: Iterator
Iterator モジュールを使うと様々な反復子 (iterator) を容易に作ることができる。
Range は最初から要素が決まっているが、Iterator の場合は要求があるごとに値を決めるところが違い、より柔軟性がある。
次にいくつかの Iterator の作り方を示す。
# Iterator https://crystal-lang.org/api/1.9.2/Iterator.html
# 元のレンジ
range1 = 1..100
# 範囲を指定して選択
iter1 = range1.select(30..33)
p iter1.to_a
# 条件を指定して選択
iter2 = range1.each.select ->(x : Int32) {x > 50 && x % 10 == 0}
p iter2.to_a
# 条件を指定して除外
iter3 = range1.each.reject ->(x : Int32) {x > 5}
p iter3.to_a
# 値を指定してステップ
iter4 = range1.each.step(30)
p iter4.to_a
# 範囲をスキップしてからステップ
iter5 = range1.each.skip(60).step(10)
p iter5.to_a
これを実行すると次のようになる。
$ ./bin/iterator
[30, 31, 32, 33]
[60, 70, 80, 90, 100]
[1, 2, 3, 4, 5]
[1, 31, 61, 91]
[61, 71, 81, 91]
$
ステップに関してはさらに柔軟性のあるステップが可能である。詳しくは Steppable モジュール参照。
次の例は Crystal のドキュメントに出ているサンプルを少し変更してコメントを追加したものである。このようにクラスを定義するとより柔軟性のある反復子を作ることができる。
# iterator2.cr
# クラスの定義
class Step2Iterator
include Iterator(Int32) # Iterator モジュールをクラスに含める。
# コンストラクタ
def initialize(@size : Int32)
@produced = 0
end
# 次の値を返すオーバーライドメソッド
def next
if @produced < @size
@produced += 2 # ステップ 2 で値を生成
else
stop # @size を超えたら終わり
end
end
end
# 結果を表示
it = Step2Iterator.new(10)
p it.to_a
この実行例を下に示す。
$ ./bin/iterator2
[2, 4, 6, 8, 10]
$
Iterator も Range 同様に each メソッドなどを使って反復処理に利用する。
iter.each do |x|
....
end
Crystal: Spec によるテスト
Crystal には Spec モジュールと言うコードテスト用のモジュールがあり、これを使って自動テストができる。
Spec のサンプル
次の例は Testing Crystal Code というページに出ているサンプルである。
require "spec"
describe Array do
describe "#size" do
it "correctly reports the number of elements in the Array" do
[1, 2, 3].size.should eq 3
end
end
describe "#empty?" do
it "is true when no elements are in the array" do
([] of Int32).empty?.should be_true
end
it "is false if there are elements in the array" do
[1].empty?.should be_false
end
end
end
describe は何のテストかと言うコメントみたいなもので、テスト用のブロックを伴い、その中に describe が入れ子になっていてもよい。
it が実際にテストを行う部分で「~は~であるべきだ。」という書き方をする。
プロジェクトでの使用例
crystal init lib コマンドでライブラリ用のプロジェクトを作ると、そのプロジェクト内に ./spec フォルダが作成される。
この中には、次のファイルが自動的に作成される。この例で spectest はプロジェクト名なのでプロジェクトごとに変わる。
spec_helper.cr
spectest_spec.cr
spec_helper.cr はテストが実行される前に実行され、テスト準備などを行う。デフォルトでは次のような内容になっている。ただし、spectest はプロジェクト名であるのでプロジェクトごとに変わる。
require "spec"
require "../src/spectest"
次にこの例でテストの対象となる src/spectest.cr の内容を示す。このモジュールには radian() と cosdeg() という関数が含まれる。
# TODO: Write documentation for `Spectest`
module Spectest
extend self
VERSION = "0.1.0"
# 度をラジアンに変換する。
def radian(theta)
theta / 180.0 * Math::PI
end
# 角度を「度」で与える cos 関数
def cosdeg(theta : Float64) : Float64
Math.cos(radian(theta))
end
end
このモジュールのテストを行う spectest_spec.cr の例を示す。この例では 90.0度に対する cosdeg() 関数のテストを行っているが、cosdeg(90.0) は完全な 0 ではないのでエラーが表示される。
require "./spec_helper"
describe Spectest do
# cosdeg() のテスト
it "cosdeg() works" do
Spectest.cosdeg(90.0).should be >= 0.0 # OK
Spectest.cosdeg(90.0).should be < 1.0e-16 # OK
Spectest.cosdeg(90.0).should eq(0.0) # 誤差があるため完全な 0.0 にはならない。
end
end
このテストを実行するには、つぎのように crystal spec コマンドを実行する。
$ crystal spec
F
Failures:
1) Spectest works
Failure/Error: Spectest.cosdeg(90.0).should eq(0.0)
Expected: 0.0
got: 6.123233995736766e-17
# spec/spectest_spec.cr:9
Finished in 191 microseconds
1 examples, 1 failures, 0 errors, 0 pending
Failed examples:
crystal spec spec/spectest_spec.cr:6 # Spectest works
Crystal に for 文がない! (Ruby との違い)
たいていのプログラミング言語では for 文というのがあって、繰り返し処理に使われる。
しかし、Crystal には for 文がない。
それではどうするかというと、Ruby にもある each メソッドなどを使うことで代用できる。
それ以外にも Ruby との違いが多くある。それらの詳細は Crystal for Rubyists に記載がある。
次にいくつかの例を示す。
型の指定
Ruby は動的に型が決まるので変数の型指定はできない。Crystal では静的に型を決めるので明示的に型指定を行うことができる。(普通は型推論で型が決められる)
例) n = 0_i16 # 16ビット整数
あるいは
n : Int16 = 0 # 16ビット整数
メソッド名の違いや別名の有無
次にこの問題の例を示す。
Ruby では配列長は .size だけでなく .length や .count で取得できる。一方、Crystal では .size のみである。
Ruby の Enumerable.detect メソッドは、Crystal では Enumerable.find になる。
文字型の存在
Ruby は動的に型が決まるので、文字列リテラルは ‘…’ と書くこともできる。しかし、Crystal では文字列と文字は別の型なので、文字列リテラルを ‘…’ と書くことはできない。
配列などの []
配列などの要素を取得するときに鍵かっこでそのインデックスを指定するが、Ruby では存在しないインデックスを指定すると nil か返される。
一方、Crystal では例外が発生する。
もし、Crystal で Ruby のように nil を受け取りたい場合は、[]? を使用する。
名前付きパラメータの使用
次のような名前付きパラメータは Ruby ではエラーになるが、Crystal では問題なく動作する。
def process_data(a, b)
# do stuff...
end
process_data(b: 2, a: "one")
クラスのプロパティの指定方法
クラスのプロパティ指定方法が両者で次のように異なる。
- attr_accessor => property
- attr_reader => getter
- attr_writer => setter
さらに Crystal では nil の可能性がある場合やブール型のプロパティには ? を付ける。
property?
getter?
クラスメソッドの使用
Ruby ではクラスメソッドの使用時、:: を使用するが、Crystal では . を使用する。
Ruby の場合 File::exists?(path)
Crystal の場合 File.exists?(path)
プライベートメソッド
Crystal ではプライベートなメソッドは下の例のように private を付ける。
private def method
42
end
疑似定数
FILE などの疑似定数に違いがある。詳細は Pseudo Contants 参照。
$ で始まる特殊変数は Crystal では使えないものがある。
例として、正規表現関連に関しては $~ や $1, $2 などは使えるが、その他は使用できない。
プロセスの終了コード $? は使えるが、パラメータの最後のインデックス $# は使えない。
rescue 句の書き方
例外処理の rescue 句だが、Ruby では rescue XXXError => e のように書くが、Crystal では rescue e: XXXError のように書く。
Crystal: ソート
Array または StaticArray の要素を並べ替えるには、sort やその系統のメソッドを使用する。
- sort 要素を昇順に並べ替え結果を返す。元の配列は変化しない。
- sort! 元の配列の要素を昇順に並べ替える。
- ブロック付きの sort 比較関数を定義し、その結果 (-1, 0, 1) によりソートする。
- sort_by 要素そのものでなく要素の属性などで比較してソートする。
ハッシュ(連想配列)には並べ替えのメソッドがないので配列に変換してから並べ替えを行う。
次に簡単なソートの例を示す。
# sort.cr
# 数値配列のソート
a = [5, 2, 0, 4]
b = a.sort
p! a # a は変化しない。
p! b # ソート結果
a.sort!
p! a # a が変化する。
c = a.sort {|x, y| y <=> x} # 逆順でソート
p! c
# StaticArray のソート
sa = StaticArray[5, 2, 0, 4] # StaticArray でも同様にできる。
sb = sa.sort
p! sb
# 文字列配列のソート
astr = ["Crystal", "is", "cool"]
bstr = astr.sort # 普通のソートだと辞書順
cstr = astr.sort_by {|x| x.size} # 長さ順にソート
p! bstr
p! cstr
# ハッシュのキーを並べ替える。
hash = {Indonesia: "Jakarta", Thailand: "Bangkok", Philippines: "Manila", Vietnam: "Hanoi"}.to_h # キーがシンボルに変換される。
p! hash
k = hash.keys.sort
p! k
このサンプルの実行例を示す。
$ ./bin/sort
a # => [5, 2, 0, 4]
b # => [0, 2, 4, 5]
a # => [0, 2, 4, 5]
c # => [5, 4, 2, 0]
sb # => StaticArray[0, 2, 4, 5]
bstr # => ["Crystal", "cool", "is"]
cstr # => ["is", "cool", "Crystal"]
hash # => {:Indonesia => "Jakarta", :Thailand => "Bangkok", :Philippines => "Manila", :Vietnam => "Hanoi"}
k # => [:Indonesia, :Philippines, :Thailand, :Vietnam]
$
Crystal: スタックなどとして使える Deque
Deque は配列に様々な機能を追加してものである。
このオブジェクトは例えば
- スタック
- キュー
- ローテーションバッファ
などとして使える。
次の例は様々なメソッドを使って Deque を操作するものである。
# Deque https://crystal-lang.org/api/1.9.2/Deque.html
q1 = Deque.new(3, 0_i32) # 長さ 3 の 0 で初期化された Deque
q2 = Deque.new([1, 2, 3]) # 配列で初期化された Deque
q3 = Deque{5, 6, 7, 8, 9} # 直接、要素を指定して初期化
p! q1
p! q2
p! q3
q1.push(10) # 最後に追加
p! q1
q1 << 11 # << は push と同じ
p! q1.size # 要素数
p! q1[0] # 先頭の要素
p! q1[q1.size-1] # 最後の要素
v1 = q1.pop # 最後を取り出す
p! v1 # 最後の要素の確認
p! q1 # q1 の内容確認
v2 = q2.shift # 先頭を取り出す。
p! v2 # 先頭の要素の確認
p! q2 # q2 の内容確認
q3.rotate! # 要素全体を回転
p! q3
q3.insert(1, 10) # 指定位置に要素を挿入
p! q3
q3.reject! {|x| x >= 10} # 条件を指定して要素を削除
p! q3
この実行例を下に示す。
$ ./bin/deque
q1 # => Deque{0, 0, 0}
q2 # => Deque{1, 2, 3}
q3 # => Deque{5, 6, 7, 8, 9}
q1 # => Deque{0, 0, 0, 10}
q1.size # => 5
q1[0] # => 0
q1[q1.size - 1] # => 11
v1 # => 11
q1 # => Deque{0, 0, 0, 10}
v2 # => 1
q2 # => Deque{2, 3}
q3 # => Deque{6, 7, 8, 9, 5}
q3 # => Deque{6, 10, 7, 8, 9, 5}
q3 # => Deque{6, 7, 8, 9, 5}
$
Crystal: 複素数
Crystal では標準で複素数がサポートされている。
ただし、複素数を使うには require “complex” が必要である。
Complex 構造体では基本的な演算や関数がサポートされているが、算術関数などは Math モジュールでサポートされている。
次に複素数の使用例を示す。
# Complex https://crystal-lang.org/api/1.9.2/Complex.html
require "complex"
# 作成
# z0 = -1.0 + 1.0i <== エラーになる。
z1 = Complex.new(1, 2) # インスタンス化
p! z1
p! 1.to_c # Number.to_c メソッド
p! 1.i # Number.i メソッド
p! Complex.zero # 0+0i
# 演算
z2 = Complex.new(-2.4, 3.6)
p! z1 + z2
p! z1 - z2
p! z1 * z2
p! z1 / z2
# 比較
p! z1 == z2
p! z1 == z1.clone
# 関数
p! z1.abs # 絶対値
p! z1.conj # 共役複素数
p! z1.real # 実部
p! z1.imag # 虚部
p! z1.inv # 反転
p! z1.polar # 極形式
p! z1.phase # 位相
この実行例を下に示す。
$ ./bin/complex
z1 # => (1.0 + 2.0i)
1.to_c # => (1.0 + 0.0i)
1.i # => (0.0 + 1.0i)
Complex.zero # => (0.0 + 0.0i)
z1 + z2 # => (-1.4 + 5.6i)
z1 - z2 # => (3.4 - 1.6i)
z1 * z2 # => (-9.6 - 1.1999999999999997i)
z1 / z2 # => (0.25641025641025644 - 0.4487179487179486i)
z1 == z2 # => false
z1 == z1.clone # => true
z1.abs # => 2.23606797749979
z1.conj # => (1.0 - 2.0i)
z1.real # => 1.0
z1.imag # => 2.0
z1.inv # => (0.2 - 0.4i)
z1.polar # => {2.23606797749979, 1.1071487177940904}
z1.phase # => 1.1071487177940904
$
Crystal: 数学関数
Crystal の数学関数は Math モジュールに含まれる。
その特徴としては算術関数だけでなく一部の特殊関数もサポートされていることである。
さらに、複素数にも対応している。
なお、abs などの関数は Int32 や Float64 などのメソッドである。
次に一部の関数の使用例を示す。
# Math https://crystal-lang.org/api/1.9.2/Math.html
require "complex"
# 平方根
p! Math.sqrt(2.0) # Float64
p! Math.isqrt(65) # Int32 (最も近い整数になる)
z1 = Complex.new(2.0, 1.0)
p! Math.sqrt(z1) # Complex
# 三角関数
x = 45.0/180.0*Math::PI
p! Math.sin(x)
p! Math.cos(x)
p! Math.tan(x)
# 逆三角関数
p! Math.asin(0.7071)
p! Math.acos(0.7071)
p! Math.atan(1.0)
# 指数関数、対数関数
p! Math.exp(0.0)
p! Math.exp2(0.0)
p! Math.log(2.0)
p! Math.log2(2.0)
p! Math.log10(10.0)
# 双曲線関数
p! Math.tanh(1.0)
# 特殊関数
p! Math.erf(-2.0) # 誤差関数
p! Math.gamma(1.0) # ガンマ関数
この実行例を下に示す。
$ ./bin/math
Math.sqrt(2.0) # => 1.4142135623730951
Math.isqrt(65) # => 8
Math.sqrt(z1) # => (1.455346690225355 + 0.34356074972251244i)
Math.sin(x) # => 0.7071067811865475
Math.cos(x) # => 0.7071067811865476
Math.tan(x) # => 0.9999999999999999
Math.asin(0.7071) # => 0.7853885733974476
Math.acos(0.7071) # => 0.785407753397449
Math.atan(1.0) # => 0.7853981633974483
Math.exp(0.0) # => 1.0
Math.exp2(0.0) # => 1.0
Math.log(2.0) # => 0.6931471805599453
Math.log2(2.0) # => 1.0
Math.log10(10.0) # => 1.0
Math.tanh(1.0) # => 0.7615941559557649
Math.erf(-2.0) # => -0.9953222650189527
Math.gamma(1.0) # => 1.0
$
Crystal: 例外処理
Crystal での例外処理は begin..rescue..ensure.. を使う所は Ruby と似ているが、異なる部分も多い。例えば
- rescue の書き方
- else がある
である。
Ruby では rescue は rescue XXXError => e のように書くが、Crystal では rescue e: XXXError と書く。
また、Crystal には else 句があり、これは例外が起きなかった時の処理を記述する。
次に例外処理の例を示す。
# 例外処理 https://crystal-lang.org/reference/1.9/syntax_and_semantics/exception_handling.html
# https://crystal-lang.org/api/1.9.2/Exception.html
# begin..rescue の使用例
begin
a = Array(Int32).new # 長さ0の整数配列
puts a[1].to_s
rescue e # 例外時に実行される
puts e.message
end
# begin..rescue..else の使用例
begin
a = Array(Int32).new(2, 0) # 長さ2の整数配列
puts a[1].to_s
rescue e
puts e.message
else # 例外が起きなかったときに実行される。
puts "OK"
end
# ensure の使用例
begin
raise("ensure test")
rescue e
puts e.message
ensure # 例外の有無に関わらず実行される。
puts "OK"
end
# 例外ごとの処理
a = {A:0, B:2}
begin
a["A"]
puts "OK"
rescue e: KeyError
puts e.message
rescue e: RuntimeError
puts e.message
rescue
puts "Fatal error"
end
# 独自例外
class MyError < Exception
def initialize
super
@message = "MyError"
end
end
begin
raise(MyError.new)
rescue e
puts e.message
end
この実行例を下に示す。
$ ./bin/exception
Index out of bounds
0
OK
ensure test
OK
OK
MyError
$
Crystal: マクロ
マクロを使うと同じような機能の関数をいくつも作らず、名前の一部を置き換えることによりひとつの関数にまとめることができる。
まず、例を挙げる。
この例は HTML タグを作る関数を作るマクロである。
ここには2つのマクロを定義しており、define_html_tag(name) は h1 タグのようなタグ内容を持つもの、そして、define_html_single_tag(name) は br タグのようなタグ内容を持たないタグを生成するマクロである。
タグ名は name としてマクロパラメータとして与えるが、その他の要素、ここでは class とタグ内容 content は関数のパラメータとして与えている。
マクロのパラメータは {{…}} で囲むことにより文字列の一部に埋め込まれる。関数パラメータは文字列の埋め込みなので #{…} で文字列内に埋め込んでいる。
# macro https://crystal-lang.org/reference/1.9/syntax_and_semantics/macros/index.html
macro define_html_tag(name)
def {{name}}(content, classes="")
if classes == ""
%(<{{name}}>#{content}</{{name}}>)
else
%(<{{name}} class="#{classes}">#{content}</{{name}}>)
end
end
end
macro define_html_single_tag(name)
def {{name}}(classes="")
if classes == ""
%(<{{name}}>)
else
%(<{{name}} class="#{classes}">)
end
end
end
define_html_tag h1
define_html_tag h2
define_html_single_tag br
p h1("H1")
p h2("H2")
p h2("H2", "text-center")
p br
p br("p-1")
この実行例を下に示す。
$ ./bin/macro
"<h1>H1</h1>"
"<h2>H2</h2>"
"<h2 class=\"text-center\">H2</h2>"
"<br>"
"<br class=\"p-1\">"
$
Crystal: アノテーション
アノテーション (Annotation) は クラス、モジュール、メソッド、パラメータなどのメタデータを定義するメカニズムである。
アノテーションは単なるヒントとして使うこともできるし、値を定義してその値を処理中に使用することもできる。
アノテーションの名前は次のように定義する。
annotation AnnotationName
end
この名前をクラス、モジュール、メソッドなどの先頭に定義して使用する。メソッドのパラメータの場合はパラメータ名の直前に定義する。
値を持たないアノテーションは単なるヒントであり、次のように使用する。
@[AnnotationName]
class Class1
....
end
値を持つアノテーションは () 内に値を定義する、値は複数でもよくカンマで区切って使用する。また、名前付きタプルの形式でもよい。
@[AnnotationName(x, y)]
def func()
...
end
次にアノテーションの使用例を示す。この例では HTML タグで内容を持たないタグのみを受け入れる関数を定義している。
# annotation https://crystal-lang.org/reference/1.9/syntax_and_semantics/annotations/index.html
annotation TagType
end
# HTML タグで内容を持たないタグのみを受け入れる
@[TagType(tagtype: :Single)]
def html_tag(name)
if {{ @def.annotation(TagType)[:tagtype] }} == :Single
%(<#{name}>)
end
end
p html_tag("br")
次の例は、HTTP ハンドラの例で、受付できる HTTP メソッドをアノテーションで定義している。
# HTTP メソッドの GET または POST を受け入れるハンドラ
@[HttpMethod(:GET, :POST)]
def handler(req, res)
if ! ({{ @def.annotation(HttpMethod)[0] }} == req.method || {{ @def.annotation(HttpMethod)[1] }} == req.method)
raise "Bad method."
end
# ハンドラの処理
200
end
p handler(req, res)
既定のアノテーション
Link アノテーションは標準ライブラリに含まれるアノテーションである。これはこのアノテーションが付いた関数のビルドに必要な外部ライブラリを指定する。
これにより、ビルド時にそのライブラリがリンクされ、その関数が正しくリンクできる。例えば、次のように Link アノテーションを付けておくとライブラリ pcre がリンクされる。
@[Link(ldflags: "-lpcre")]
また、次のように Link アノテーションを付けておくと、共有ライブラリ pcre を探してリンクしてくれる。
@[Link("pcre")]
C のライブラリ関数を使用する際に、ヘッダファイル (*.h) が必要になるが、そのヘッダファイルについての情報は Include アノテーションを使うと解決できる。Include アノテーションの例は下記の Qiita の記事に出ている。
CrystalでC言語のバインディングを自動生成する crystal_lib
その一部の例を下に挙げる。
@[Include("event.h", prefix: %w(event_ EVENT_ Event))]
Crystal: Colorize で表示をカラフルに
Colorize モジュールのメソッドを使うと、カラー対応端末 (アプリケーション、例えば Tera term Pro) で表示文字列に色を付けたり、下線などの文字飾りをつけることができる。
簡単な Colorize の使用例を示す。
# colorize https://crystal-lang.org/api/1.9.2/Colorize.html
require "colorize"
puts "Green".colorize(:green)
puts "After Green"
puts "Blue".colorize.blue
puts "Red Underline".colorize.fore(:red).mode(:underline)
puts "Light Yellow Backgound White".colorize.fore(:light_yellow).back(:dark_gray)
puts "Reset".colorize.default
Crystal: Slice
Slice は指定した型の指定したサイズのバッファの先頭を指すポインタである。
ただし、Pointer とは異なり unsafe ではない。
Slice は 抽象クラス IO で入出力バッファとしてよく使用される。
次に Slice の一部のメソッドの使用例を示す。
# slice https://crystal-lang.org/api/1.9.2/Slice.html
buf = Slice.new(4, sizeof(Int8))
buf.fill(0) # 0 フィル
buf[0] = 8 # 値の変更
buf[1] = 16 # 値の変更
p! buf[1] # 値の確認
buf.each do |b| # 内容をすべて表示
p b
end
ub = buf.to_unsafe # ポインタへ変換
p typeof(ub)
buf2 = Slice.new(4, sizeof(Int8))
buf.copy_to(buf2) # buf の内容を buf2 へコピー
p buf2.to_a # 配列に変換
buf2.reverse! # 要素を逆順にする。
p buf2
このサンプルの実行例を下に示す。
$ ./bin/slice
buf[1] # => 16
8
16
0
0
Pointer(Int32)
[8, 16, 0, 0]
Slice[0, 0, 16, 8]
$
Crystal: プロセスとパイプ
Crystal でプロセスを起動するには、Process クラスを利用する。
Process クラスのコンストラクタは次のようになっている。コンストラクタを実行すると Process オブジェクトが作成されて、直ちに実行が始まる。
new(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, input : Stdio = Redirect::Close, output : Stdio = Redirect::Close, error : Stdio = Redirect::Close, chdir : Path | String | Nil = nil)
プロセスが実行されて終了するまえに、起動したプログラムが終了しないようにするためには、wait メソッドを実行する。
コンストラクタでプロセスオブジェクトを作らず、単純に他のコマンドを実行する場合は、クラスメソッドの run を使用するほうが簡単である。
次に Process クラスの簡単な使用例を示す。
# プロセス https://crystal-lang.org/api/1.9.2/Process.html
# プロセスオブジェクトを作成する。(そして直ちに実行される)
p1 = Process.new("ls", {"-l", "/"}, shell:true, output:STDOUT) # ルートディレクトリ / の内容一覧を表示
p1.wait # プロセスが終わるまで待つ。
# クラスメソッド run でコマンドを実行する。
sts = Process.run("python3", {"-V"}, output:STDOUT) # python3 のバージョンを表示。python3 コマンドはシェルスクリプトでないので shell:true にしなくてよい。
p! sts.exit_status
# Ubuntu のバージョンを表示するシェルスクリプトを実行する。
Process.run("~/bin/ver", shell:true, output:STDOUT) # シェル機能 (ここでは ~) を使う場合は shell:true にしないとエラーになる。
# ブロック付きバージョンの run
Process.run("perl", {"-v"}) { |pr|
puts "pid = #{pr.pid} OK"
}
IO.pipe を使ってリダイレクトもできる。
# pipe
# プロセスにパイプで入力し、結果を標準出力に出力する。
r, w = IO.pipe(false, false)
w.puts "from the pipe."
w.close
Process.new("cat", shell:true, input:r, output:STDOUT).wait
# プロセスの出力だけをパイプで受け取る。
r2, w2 = IO.pipe(false, false)
Process.new("./hello_world.cgi", shell:true, input:r2, output:w2).wait
w2.close
r2.gets_to_end.lines.each do |line|
puts line
end
Crystal: Signal
Signal は POSIX システムのシグナルを安全に取り扱える列挙型 (enum) である。
そして、シグナルはプロセス間で信号を送るときに使用される。
この列挙型には次の既定のシグナルが定義されている。
INT = 2
ILL = 4
FPE = 8
SEGV = 11
TERM = 15
ABRT = 6
HUP = 1
QUIT = 3
TRAP = 5
IOT = 6
KILL = 9
BUS = 7
SYS = 31
PIPE = 13
ALRM = 14
URG = 23
STOP = 19
TSTP = 20
CONT = 18
CHLD = 17
TTIN = 21
TTOU = 22
IO = 29
XCPU = 24
XFSZ = 25
VTALRM = 26
USR1 = 10
USR2 = 12
WINCH = 28
PWR = 30
STKFLT = 16
UNUSED = 31
このうち、INT は「割り込み」を意味し、INT シグナルはデフォルト動作としてアプリケーションを終了させる。
これはキーボードで Ctrl+C を押したとき発生する。
シグナルをトラップするには、trap メソッドを使う。このメソッドのブロックでそのシグナルのデフォルト動作を変更する。
デフォルト動作に戻すには、reset メソッドを使用する。
次に、シグナルの簡単な使用例を示す。
# Signal https://crystal-lang.org/api/1.9.2/Signal.html
puts "Ctrl+C を押すとプログラムが終了します。(デフォルト動作 3 秒間)"
sleep 3
# 3秒後に Ctrl+C のトラップが有効になる。
Signal::INT.trap do
puts "\nCtrl+C がトラップされました!" # Ctrl+C を入力すると、このメッセージが表示される。
end
puts "\nCtrl+C のトラップが有効になりました。(3秒間)\n"
sleep 3
# さらに 3 秒待つとトラップがリセットされる。
Signal::INT.reset
puts "\nCtrl+C がデフォルト動作に戻りました。"
sleep 3
puts ".. Done."
このプログラムの実行例を下に示す。
$ ./bin/signal
Ctrl+C を押すとプログラムが終了します。(デフォルト動作)
Ctrl+C のトラップが有効になりました。(3秒間)
^C
Ctrl+C がトラップされました!
^C
Ctrl+C がトラップされました!
^C
Ctrl+C がトラップされました!
^C
Ctrl+C がトラップされました!
^C
Ctrl+C がトラップされました!
Ctrl+C がデフォルト動作に戻りました。
^C
$
Crystal: OptionParser
コンソールアプリケーションを作るときに、機能やオプションをいろいろ指定する必要があることがある。
そのような場合、OptionParser クラスを使うと、複雑なオプションを実現するのが非常に楽である。
OptionParser では次のような形式のコマンドオプションが基本とされる。(これとは別の GNU 形式のオプションにも対応している)
コマンド名 サブコマンド名 オプション(複数) コマンド引数(複数)
次の例は OptionParser の説明のところに出ているサンプルに足りない以下の機能を追加したものである。
- 不正なオプションがあったときの処理
- コマンド引数の取得
# OptionParser https://crystal-lang.org/api/1.9.2/OptionParser.html
require "option_parser"
# サブコマンドフラグを初期化
verbose = false
salute = false
welcome = false
name = "World"
# ARGV に対いてパーサを構築
parser = OptionParser.new do |parser|
# ヘルプ用のバナー
parser.banner = "Usage: example [subcommand] [arguments]"
# サブコマンドハンドラ
parser.on("salute", "Salute a name") do
salute = true
parser.banner = "Usage: example salute [arguments]"
parser.on("-t NAME", "--to=NAME", "Specify the name to salute") { |_name| name = _name }
end
# サブコマンドハンドラ
parser.on("welcome", "Print a greeting message") do
welcome = true
parser.banner = "Usage: example welcome"
end
# オプションハンドラ
parser.on("-v", "--verbose", "Enabled verbose output") { verbose = true }
parser.on("-h", "--help", "Show this help") do
puts parser
exit
end
end
# サポートしていないオプションが使われたときはエラーメッセージを表示する。
parser.invalid_option do |flag|
STDERR.puts "ERROR: #{flag} is not a valid option."
STDERR.puts parser
exit(1)
end
# ファイル名などのメインパラメータ配列
parser.unknown_args do |a|
p a
end
# ARGV を解析する。
parser.parse
# サブコマンドフラグに対応した処理
if salute
STDERR.puts "Saluting #{name}" if verbose
puts "Hello #{name}"
elsif welcome
STDERR.puts "Welcoming #{name}" if verbose
puts "Welcome!"
else
puts parser
exit(1)
end
この例では、サブコマンドが salute, welcome であり、そのぞれ異なるメッセージを表示する。
また、オプションは verbose と help があり、それぞれ短い形式 (-v, -h) と長い形式 (–verbose, –help) をサポートしている。
この実行例を示す。
$ bin/option_parser -h
Usage: example [subcommand] [arguments]
salute Salute a name
welcome Print a greeting message
-v, --verbose Enabled verbose output
-h, --help Show this help
$ $ bin/option_parser salute --verbose
[]
Saluting World
Hello World
$ $ bin/option_parser salute arg1 arg2
["arg1", "arg2"]
Hello World
$ bin/option_parser welcome arg1
["arg1"]
Welcome!
$
Crystal: 既定の例外クラス
例外が発生すると、例外クラスがインスタンス化され rescue 句でキャッチできる。
すべての例外の基底クラスは Exception であり、このクラスから派生した多数のクラスが存在する。
以下にそれらを挙げる。
- ArgumentError
- Channel/ClosedError
- CSV/MalformedCSVError
- Digest/FinalizedError
- DivisionByZeroError
- Enumerable/EmptyError
- Enumerable/NotFoundError
- File/AccessDeniedError
- File/AlreadyExistsError
- File/BadExecutableError
- File/BadPatternError
- File/NotFoundError
- Server/ClientError
- IndexError
- InvalidByteSequenceError
- IO.EOFError
- IO.TimeoutError
- JSON/SerializableError
- KeyError
- NilAssertionError
- NotImplementedError
- OpenSSL/Digest/UnsupportedError
- OverflowError
- RuntimeError
- Socket/ConnectError
- System/Group/NotFoundError
- System/User/NotFoundError
- SystemError
- Time/FloatingTimeConversionError
- Time/Location/InvalidLocationNameError
- Time/Location/InvalidTimezoneOffsetError
- Time/Location/InvalidTZDataError
- WinError
これらを rescue 句で使う場合、Ruby と書き方が異なるので注意すること。下に例を示す。
a = {A:0, B:2}
begin
a["A"]
puts "OK"
rescue e: KeyError
puts e.message
rescue e: RuntimeError
puts e.message
rescue
puts "Fatal error"
end
Crystal: 無名関数
他の言語ではたいていブロックは独自のスタックフレームを持つコードの塊りという扱いである。
一方、Crystal (Ruby も) では独自のスタックフレームを持つコードの塊りであることには変わりないが、外部からパラメータを渡し、最後に実行された式の値を返すことできる。
他の言語で言えば、無名関数や匿名関数、クロージャと呼ばれるものに近い。
そして、ブロックは他の関数のパラメータとして渡すことができるのも無名関数と同じである。
ブロックがパラメータであることを示すにはパラメータの名前に & を付ける。
次の例は、ブロックを関数パラメータとして渡すものである。
例1 ブロックを実行する関数 int_to_int を変数 proc に代入して call メソッドで実行する。
def int_to_int(&block : Int32 -> Int32)
block
end
proc = int_to_int { |x| x + 1 }
n = proc.call(1) # => 2
puts n.to_s
例2 ブロックをコールバックするイベントハンドラ on_save を定義し、それをイベント発生関数 save により動作させる。
class Model
def on_save(&block)
@on_save_callback = block
end
def save
if callback = @on_save_callback
callback.call
end
end
end
model = Model.new
model.on_save { puts "Saved!" }
model.save # prints "Saved!"
次の例はアロー関数を変数 procdef に代入し、それをブロックをパラメータとする関数 some_proc に渡した関数を変数 proc に代入し、call メソッドで呼び出して使用する。
例3
def some_proc(&block : Int32 -> Int32)
block
end
x = 0
procdef = ->(i : Int32) { x += i }
p procdef
proc = some_proc(&procdef)
p proc
y = proc.call(1) # => 1
p y
y = proc.call(10) # => 11
p x # => 11
p y
例4 自分で定義したブロック付きメソッドでブロックを呼び出すには yield を使う。
def foo
yield(1,2)
end
foo {|a,b|
p a + b
Crystal: Nilable な変数
Crystal には nil を取るかもしれない変数を定義できる。例えば、HTTP::Request.body は次のような型を持つ。これは Nil かもしれない IO 型という意味である。
def body: IO | Nil
そのため、直接、IO のメソッドを使うことができない。
しかし、nil でないことを確認してから、次の例のように as を使って強制的に型を IO にすることで IO のメソッドが使用できる。
unless response.body.nil?
bs = response.body.as(IO).getb_to_end
end
Type refrection には他にも型に関するいろいろなメソッドの説明がある。
- is_a? s.is_a?(String) は s が文字列なら true を返す。
- nil? a.nil? は a が nil なら true を返す。
- respond_to? a.response_to(:abs) は変数 a が abs というメソッドを持てば、true を返す。
- as response.body.as(IO) は nilable な変数を nilable でなくする。
- as? as と機能的に同じだが、キャストできない時は nil を返す。
- typeof typeof(a) は変数 a の型を返す。例えば、a が32ビット整数なら Int32 を返す。