あなたも書ける!Ruby DSLの記述方法まとめ


「メタプログラミングRuby」第四章後半にて、Rubyで記述するDSLに関する説明がされています。

今回は、Rubyで書くDSLの書き方の基本事項に関してまとめていきます。

DSLとは?

ドメイン固有言語(ドメインこゆうげんご、英: domain-specific language、DSL)とは、特定のタスク向けに設計されたコンピュータ言語である。ドメイン特化言語あるいは単にドメイン言語とも呼ばれるが、学術的にはドメイン特化言語と呼ばれることが多い。C言語やJavaのような汎用のプログラミング言語の対照とされる。
(Wikipedia)

個人的には、小難しい実装を内部に隠蔽し、「ソースコードをまるで文章のように」読むことができることが、DSLを実装する最大のメリットと考えています。

身近にあるRuby DSLの例

Rake

RakeはRuby DSLの仕組みを用いて記述されるビルドツールです。

例えば、以下のRakefileを見てみましょう。

Rakefile

task :hello_task do
  puts 'Rake Task Start...'
  puts 'Hello,RakeTask!'
  puts 'Rake Task End.'
end

あたかも人間がやることリストを書き下すように、Rubyの振る舞いを定義しています。

実行

rake hello_task

Itamae

ItamaeはRubyで記述された、ChefやAnsibleより軽めのサーバープロビジョニングツールです。

recipe.rbに記述されたDSLを元に、サーバーをセットアップすることができます。

# recipe.rb

# 例えばnginxをインストールする
package :nginx do
  action :install
end

「上から下へ、まるで英語の文章を読むように」設定を記述することができていますね。

例えばItamaeの仕組み・実装を知らなくても、このrecipe.rbを見るだけで、「あ、nginxのパッケージをインストールしているっぽい」という予測をすることができます。

DSLの原理

以下のコードを見てみましょう。極めてシンプルな、入門書の第一章に出てくるようなコードです。


def say(message)
    puts message
end

say 'Hello'

トップレベルに関数sayを定義し、下で引数文字列’Hello’ を渡して呼び出しています。

このコードを、以下のように書き換えてみます。

dsl.rb


def say(message)
  puts message
end

load 'hello_dsl.rb'

Kernel#load はKernel#requireとは違い、読み込んだコードを実際に実行する関数です。

hello_dsl.rb


say 'Hello,DSL!'

hello_dsl.rbだけみると、「DSLっぽく」なってきました。

ruby dsl.rb #=> Hello,DSL!

ブロックを渡す

ブロックを渡すにも、「DSLだから」特別な書き方をしているのではなく、実はシンプルなyieldで実装できます。

dsl.rb


def say(message)
  puts message
end

def dosomething(&block)
  yield if block_given?
end

def greet
  say 'Hello'
end

load 'hello_dsl.rb'

hello_dsl.rb


dosomething do
  greet
  say 'DSL Programming is not difficult'
end

「難しそう」なイメージを捨てて読んでみると、実は上の行で定義されたメソッドに対して適切な引数を呼び出しているだけ、ということがわかります。

実例:簡単なTodoListを作る

DSLの例として簡単なDSLを実際に作ってみます。

  • Todoリストを記述できる
  • DSLを実行すると、TODOリストの中で未完了なものが表示される

というシンプルなものを作ってみましょう。

DSLのコア部分は以下のようになります。
todo_dsl.rb

require 'date'

def todolist(&block)
  yield if block_given?
end

def todo(taskname,deadline:nil)
  now = Date.today
  task_deadline = Date.parse(deadline)
  if now > task_deadline
    puts "「#{taskname}」が未完了です"
  end
end

load 'todolist.rb'

DSLとして記述すべきファイルは以下のように書きます。

todolist.rb

todolist do
  todo 'ブログ記事を書く', deadline: '2017-12-01'
  todo '部屋の掃除', deadline: '2017-11-15'
  todo 'メタプログラミングRubyを読む', deadline: '2017-11-30'
end

紙で管理するTODOリストにだいぶ近い感じで記述できているのではないでしょうか。

これなら書式を簡単に共有してあげるだけで、プログラマーでない人でもこのファイルを書くことができそうです。

実行してみましょう。

ruby todo_dsl.rb #=>「部屋の掃除」が未完了です

できましたね。