これは何
Rubyは「DSLが書きやすい言語」という説明がされることがあります。
なぜRubyはDSLが書きやすい、と言われるのかを僕なりにまとめてみました。
「こういう要素もあるからだよ」などあればぜひコメントや編集リクエストをいただけると嬉しいです。
DSLの例
DSLとは直訳するとドメイン固有言語です。
簡単なイメージで言うと、「ユーザーが自由に構文に近い仕組みを実装、提供できる」ようなものです。
詳しい説明はWikipediaにお任せします。
RubyやRailsで使われているDSLで有名なものはActiveRecord周りの実装などでしょうか。
例を記載します。
class User < ApplicationRecord
validates :name, presence: true
has_many :articles
end
このような形で、RailsのActiveRecord固有の構文を使うことで、バリデーションの設定やリレーションの設定などを簡単に行うことができます。
このようなDSLをなぜRubyでは実現できるのかを整理します。
DSLを実現するために使われているRuby特性
Rubyには他の言語では珍しい特性がいくつかあります。
それらの特性について説明します。
1. メソッドの呼び出し時に()を省略できる
Rubyはメソッドを呼び出すときに()
を省略することができます。
これにより、構文の宣言のようにメソッドの呼び出しをコードとして記述することができます。
def hello(args)
"Hello #{args}"
end
hello 'world' #=> 'Hello world'
2. ブロックを引数として渡せる
Rubyはメソッドに、他の言語でいうブロックを引数として定義できます。
詳しい仕組みは以下をご覧ください
これにより、他の言語では構文でしか使えないことが多いブロック記法を伴うようなコードも簡単に実装が行えます
例は以下の通りです。
def two(&block)
2.times do |i|
block.call(i)
end
end
two do |i|
puts "This is #{i+1} times"
end
3. クラス宣言のブロック内で直接メソッドの実行ができる
Rubyでは、クラスの宣言時にクラス宣言のブロック内で直接メソッドを実行できます。
例えば以下のようなコードは問題なく動作します。
class Base
class << self
def hello(arg)
pp "Hello #{arg}"
end
end
end
class Example < Base
hello 'world'
end
#=> 'Hello world'
4. define_methodなどで、動的にメソッドの定義を行うことができる
Rubyはdefine_method
やdefine_singleton_method
などで動的にメソッドの宣言を行うことができます。
これにより、メソッドで受け取った値を元にクラスにメソッドの定義を行うことなどが簡単にできます。
これらの仕組みについては以下の記事を参考にしてください。
def define_call(&block)
define_method :call do
block.call
end
end
define_call do
pp 'Hello world'
end
call #=> 'Hello world'
その他の特性
そのほかにも、DSLを作ってく上でよく使われる特性はあります。
詳しくは以下の記事をご覧ください。
- instance_eval(@kekemoto さんに教えていただきました!)
DSLを書いてみる
整理した特性を用いて実際にDSLを書いてみます。
今回はメソッドに制限時間を設定するためのDSLを実装してみました。
require 'timeout'
class Timer
class << self
def timeout(t)
@@time = t
end
def process(&block)
define_method :call do
puts 'start'
Timeout.timeout(@@time) do
block.call
end
puts 'success'
rescue Timeout::Error
puts "timeout"
end
end
end
end
class Example < Timer
timeout 1
process do
sleep 0.5
end
end
Example.new.call
まとめ
このように、Rubyはいくつかの特性が合わさることにより、特定のドメインに特化したDSLを簡単に実装をすることができます。
ぜひ皆さんも色々な実装を試してみてください。