26
Help us understand the problem. What are the problem?

posted at

updated at

Organization

RubyでDSLが書きやすい理由を整理する

これは何

Rubyは「DSLが書きやすい言語」という説明がされることがあります。
なぜRubyはDSLが書きやすい、と言われるのかを僕なりにまとめてみました。
「こういう要素もあるからだよ」などあればぜひコメントや編集リクエストをいただけると嬉しいです。

DSLの例

DSLとは直訳するとドメイン固有言語です。
簡単なイメージで言うと、「ユーザーが自由に構文に近い仕組みを実装、提供できる」ようなものです。
詳しい説明はWikipediaにお任せします。

RubyやRailsで使われているDSLで有名なものはActiveRecord周りの実装などでしょうか。
例を記載します。

user.rb
class User < ApplicationRecord
  validates :name, presence: true
  has_many :articles
end

このような形で、RailsのActiveRecord固有の構文を使うことで、バリデーションの設定やリレーションの設定などを簡単に行うことができます。
このようなDSLをなぜRubyでは実現できるのかを整理します。

DSLを実現するために使われているRuby特性

Rubyには他の言語では珍しい特性がいくつかあります。
それらの特性について説明します。

1. メソッドの呼び出し時に()を省略できる

Rubyはメソッドを呼び出すときに()を省略することができます。
これにより、構文の宣言のようにメソッドの呼び出しをコードとして記述することができます。

example.rb
def hello(args)
  "Hello #{args}"
end

hello 'world' #=> 'Hello world'

2. ブロックを引数として渡せる

Rubyはメソッドに、他の言語でいうブロックを引数として定義できます。
詳しい仕組みは以下をご覧ください

これにより、他の言語では構文でしか使えないことが多いブロック記法を伴うようなコードも簡単に実装が行えます

例は以下の通りです。

example.rb
def two(&block)
  2.times do |i|
    block.call(i)
  end
end

two do |i|
  puts "This is #{i+1} times"
end

3. クラス宣言のブロック内で直接メソッドの実行ができる

Rubyでは、クラスの宣言時にクラス宣言のブロック内で直接メソッドを実行できます。
例えば以下のようなコードは問題なく動作します。

example.rb
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_methoddefine_singleton_methodなどで動的にメソッドの宣言を行うことができます。
これにより、メソッドで受け取った値を元にクラスにメソッドの定義を行うことなどが簡単にできます。
これらの仕組みについては以下の記事を参考にしてください。

example.rb
def define_call(&block)
  define_method :call do
    block.call
  end
end

define_call do
  pp 'Hello world'
end


call #=> 'Hello world'

その他の特性

そのほかにも、DSLを作ってく上でよく使われる特性はあります。
詳しくは以下の記事をご覧ください。

DSLを書いてみる

整理した特性を用いて実際にDSLを書いてみます。
今回はメソッドに制限時間を設定するためのDSLを実装してみました。

example.rb
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を簡単に実装をすることができます。
ぜひ皆さんも色々な実装を試してみてください。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
26
Help us understand the problem. What are the problem?