RSS

カテゴリー別アーカイブ: Crystal

Crystal: モジュール

モジュールとは module キーワードで囲まれたブロックであり、次の用途のため作成する。

  • 名前空間として
  • 他のブロック (class, module) の内容の一部として

名前空間として使う例を下に示す。

module Curses
  class Window
  end
end

Curses::Window.new

クラスの一部として使う例を下に示す。

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

include でなく extend を使ってモジュールをクラスや他のモジュールに含める場合は、そのモジュールのメソッドをクラスメソッドとして使用できる。

module SomeSize
  def size
    3
  end
end

class Items
  extend SomeSize
end

Items.size # => 3

extend に self を指定すると、そのモジュールのメソッドを公開できる。

module Base64
  extend self

  def encode64(string)
    # ...
  end

  def decode64(string)
    # ...
  end
end

この Base64 のメソッドは次のように使用できる。

Base64.encode(“abc”)

 
コメントする

投稿者: : 2023/11/20 投稿先 Crystal

 

タグ: , ,

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 を返す。

 
コメントする

投稿者: : 2023/10/07 投稿先 Crystal

 

タグ: , ,

Crystal: HTTP/Params

HTTP/Params はクライアントからのリクエストのパラメータを表す。GET メソッドで言えば URL の ? 以降の部分に当たる。

実は、HTTP / Params は URI / Params の別名である。

実際のパラメータを取得するには Request.query_params にアクセスすることにより簡単にリクエストパラメータを取得できる。

Request.query_params の型は URI::Params であり、HTTP サーバがリクエストがあるとパラメータを解析して、この Request.query_params に値を入れてくれる。

URI::Params は Enumerable({String, String}) を含んでいるので、ハッシュ (連想配列) としても扱うことができる。

要するに、パラメータのキーが key のとき、その値は次のようにして取得できる。

value = query_params[key]

キーが存在しない場合もあるので、その時は fetch(key, default) メソッドを使うほうが便利である。このメソッドを使えば、キーが存在しない時は、デフォルト値を返してくれる。

次に、URI:: Params の使用例を示す。

# HTTP/Params https://crystal-lang.org/api/1.9.2/URI/Params.html
require "http/server"

server = HTTP::Server.new do |context|
  req = context.request
  res = context.response
  res.content_type = "text/plain"
  if req.path == "/echo"
    res.puts req.query  # QUERY_STRING の内容
    res.puts req.query_params["message"]  # リクエストパラメータの値を得る。(エコーする)
    if req.query_params["A"]?.nil?  # パラメータがあるか確認 (has_key? のほうが良いが)
      puts "params[A] = Nil"
    else
      puts req.query_params["A"]
    end
  elsif req.path == "/fetch"
    res.puts req.query_params.fetch("A", "Default_A")  # リクエストパラメータがない場合、デフォルト値を返す。
  else
    res.status = HTTP::Status::NOT_FOUND
    res.puts "Not Found\n"
  end
end

address = server.bind_tcp 8080
puts "Listening on http://#{address}"
server.listen

(注意) POST メソッドの場合は、req.query_params でなく req.form_params に変更すること。

 
コメントする

投稿者: : 2023/10/05 投稿先 Crystal

 

タグ: , ,

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
}
 
コメントする

投稿者: : 2023/10/03 投稿先 Crystal

 

タグ: , , , ,

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
 
コメントする

投稿者: : 2023/10/03 投稿先 Crystal

 

タグ: ,

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!
$
 
コメントする

投稿者: : 2023/10/03 投稿先 Crystal

 

タグ:

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
$
 
コメントする

投稿者: : 2023/10/03 投稿先 Crystal

 

タグ: ,

Crystal: UDP サーバとクライアント

UDPSocket クラスは UDP 通信を行うためのソケットである。TCPSocket が TCP 層で動作するのに対して UDPSocket は IP 層で動作する。

TCP 層は IP 層の上に構築されていてより上位のプロトコルであり、ストリームを扱いエラー監視なども行えるが、UDP はパケット通信でエラー監視はより簡略である。

そのため、インターネット上で UDP 通信は特殊なプロトコルだけで使われ、信頼性の高い通信には TCP が使用される。

次に、UDPSocket を使った簡単なプログラムを示す。

UDP サーバ

# UDPSocket server https://crystal-lang.org/api/1.9.2/UDPSocket.html
require "socket"

# サーバを作成
server = UDPSocket.new
server.bind "localhost", 1234
puts "Create server localhost:1234"

# クライアントからのメッセージを受信する。
message, client_addr = server.receive
p message
p client_addr

# 接続を閉じる
server.close
puts "The connection closed."

UDP クライアント

# UDPSocket client https://crystal-lang.org/api/1.9.2/UDPSocket.html
require "socket"

# クライアントを作成してサーバに接続する。
client = UDPSocket.new
client.connect "localhost", 1234
puts "Create the client localhost:1234"

# サーバへメッセージを送る。
begin
  client.send "Message from the client."
rescue ex : Socket::ConnectError
  STDERR.puts ex.message
end

# 接続を閉じる。
client.close
puts "The connection closed."

これらのプログラムの使用例を下に示す。

UDP サーバ

$ ./bin/udp_server 
Create server localhost:1234
"Message from the client."
Socket::IPAddress(127.0.0.1:48096)
The connection closed.
$

UDP クライアント

$ ./bin/udp_client 
Create the client localhost:1234
The connection closed.
$
 
コメントする

投稿者: : 2023/10/02 投稿先 Crystal

 

タグ:

Crystal: UNIX サーバとクライアント

UNIXServer は、TCP サーバと異なり、他のコンピュータ上のプロセスとは通信できない。

したがって、同じコンピュータ上で動作しているプロセス間通信に使用される。

同じコンピュータ内のプロセスとの通信なので、IP アドレスやポートは使用せず、その代わりにファイルパスを使用する。このファイルパスはユニークでなけらばならず、そのファイルがすでに存在した場合はエラーとなる。

UNIX サーバは UNIXServer クラスを使用するが、UNIX クライアントは UNIXSocket クラスを使用する。

次に簡単な例を示す。

UNIX サーバ

# UNIXServer https://crystal-lang.org/api/1.9.2/UNIXServer.html
require "socket"

# クライアントからのメッセージを受信してそのままエコーする。
def handle_client(client)
  message = client.gets
  puts message
  client.puts message
end

# UNIX サーバを構築
PATH = "/tmp/myapp1.sock"
if File.delete?(PATH)  # 以前のセッションの PATH が残っていたら削除する。
  puts "Deleted former #{PATH}."
end
puts "Started with #{PATH}."
server = UNIXServer.new(PATH)
# クライアントからの接続待ち
while client = server.accept?
  puts "Accepted .."
  spawn handle_client(client)  # 接続したら handle_client メソッドをファイバで起動する。
end

UNIX クライアント

# UNIXSocket https://crystal-lang.org/api/1.9.2/UNIXSocket.html
require "socket"
PATH = "/tmp/myapp1.sock"

# UNIXSocket オブジェクトを作成する。
sock = UNIXSocket.new(PATH)
sock.puts "The Message from UNIX client."  # メッセージを送る。
response = sock.gets  # 応答メッセージ
p response
sock.close
puts "The UNIX client closed."

次にこれらのプログラムの動作例を示す。

UNIX サーバ

$ ./bin/unix_server
Deleted former /tmp/myapp1.sock.
Started with /tmp/myapp1.sock.
Accepted ..
The Message from UNIX client.

UNIX クライアント

$  ./bin/unix_client
"The Message from UNIX client."
The UNIX client closed.
$
 
コメントする

投稿者: : 2023/10/02 投稿先 Crystal

 

タグ: ,

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
$
 
コメントする

投稿者: : 2023/10/02 投稿先 Crystal

 

タグ: