the hitchhiker’s guide to dsl

Post on 11-May-2015

2.163 Views

Category:

Technology

4 Downloads

Preview:

Click to see full reader

DESCRIPTION

Ruby勉強会-8

TRANSCRIPT

2008-04-26(土) ;北海道情報大学札幌サテライトRuby勉強会@札幌-8

DSLヒッチハイク・ガイド

The Hitchhiker’s Guide To DSL

日本Rubyの会 / Ruby札幌島田浩二

snoozer.05@ruby-sapporo.org

Don’t Panic!パニクるな

ゴール

✓ Rubyを使ってDSLを作成する際の作戦の立て方と表現のポイントを知る

ゴール

どうぞよろしく

お願いします

DSLとは

DSLDomain Specific Language

✓ ドメインに存在する問題の解決に特化してデザインされたプログラミング言語

DSL

✓ ドメインに存在する問題の解決に特化してデザインされたプログラミング言語

DSL

✓ ドメインの専門用語を反映し、簡潔な方法でドメインの専門知識を表現するもの

DSL

✓ ドメインの専門用語を反映し、簡潔な方法でドメインの専門知識を表現するもの

DSLドメインの専門知識DSL

システム

ドメイン専門家

プログラマ

✓ DSLとそれを解釈するためのプログラミング言語との関係によって分類

DSL

二種類

内部DSL

外部DSLと

内部DSL

外部DSLと内部DSL

汎用言語透過的

内部DSL

外部DSLとRails

Ruby透過的

内部DSL

外部DSLと

外部DSLDSLパーサ

汎用言語

解釈

内部DSL

外部DSLと

XMLAnt

Java

解釈

内部DSL

外部DSLと

✓ ドメインの専門用語を反映し、簡潔な方法でドメインの専門知識を表現するもの

DSL

example

builder = Builder::XmlMarkup.new(:target => STDOUT, :indent => 2)builder.person do |b| b.name “shimada” b.phone “12345678” b.address “Sapporo, Japan”end

<person> <name>shimada</name> <phone>12345678</phone> <address>Sapporo, Japan</address></person>

つくり方から理解を深める

DSLのつくりかた

お題

アンケート作成を支援するDSL

http://www.flickr.com/photos/koichiwb/2132785126/

アンケート作成DSL

http://www.flickr.com/photos/koichiwb/2132785126/

✓ 質問と選択肢を入力✓ HTMLに出力

DSLじゃない実現

✓ 1つのメインクラス✓ 2つのバリュー・オブジェクト✓ 1つのライブラリ

DSLじゃない実現

DSLじゃない実現

QuestionQuestionQuestionContext

Questionnaire ERB

DSLじゃない実現

QuestionQuestionQuestionContext

Questionnaire ERB

アンケート全体を表すバリューオブジェクト

質問を表すバリューオブジェクト

アンケートを出力するメインクラス

# Non-DSL Samplectx = Context.new("Ruby勉強会-8のアンケート",[])

ctx.questions << Question.new("勉強会への参加は初めてですか?", ["初めて", "常連"])

ctx.questions << Question.new("今回の勉強会はどうでしたか?", ["良かった", "まあまあ", "最低"])

ctx.questions << Question.new("次回も参加したいですか?", ["したい", "内容によって", "二度とくるか"])

Questionnaire.create_html(ctx)

# Non-DSL Samplectx = Context.new("Ruby勉強会-8のアンケート",[])

ctx.questions << Question.new("勉強会への参加は初めてですか?", ["初めて", "常連"])

ctx.questions << Question.new("今回の勉強会はどうでしたか?", ["良かった", "まあまあ", "最低"])

ctx.questions << Question.new("次回も参加したいですか?", ["したい", "内容によって", "二度とくるか"])

Questionnaire.create_html(ctx)

# Non-DSL Samplectx = Context.new("Ruby勉強会-8のアンケート",[])

ctx.questions << Question.new("勉強会への参加は初めてですか?", ["初めて", "常連"])

ctx.questions << Question.new("今回の勉強会はどうでしたか?", ["良かった", "まあまあ", "最低"])

ctx.questions << Question.new("次回も参加したいですか?", ["したい", "内容によって", "二度とくるか"])

Questionnaire.create_html(ctx)

# Non-DSL Samplectx = Context.new("Ruby勉強会-8のアンケート",[])

ctx.questions << Question.new("勉強会への参加は初めてですか?", ["初めて", "常連"])

ctx.questions << Question.new("今回の勉強会はどうでしたか?", ["良かった", "まあまあ", "最低"])

ctx.questions << Question.new("次回も参加したいですか?", ["したい", "内容によって", "二度とくるか"])

Questionnaire.create_html(ctx)

# Non-DSL Samplectx = Context.new("Ruby勉強会-8のアンケート",[])

ctx.questions << Question.new("勉強会への参加は初めてですか?", ["初めて", "常連"])

ctx.questions << Question.new("今回の勉強会はどうでしたか?", ["良かった", "まあまあ", "最低"])

ctx.questions << Question.new("次回も参加したいですか?", ["したい", "内容によって", "二度とくるか"])

Questionnaire.create_html(ctx)

class Question attr_accessor :title, :answers def initialize title, answers @title = title @answers = answers endend

class Context attr_accessor :title, :questions def initialize title, questions @title = title @questions = questions endend

class Questionnaire TEMPLATE = ... def self.create_html(context) puts ERB.new(TEMPLATE).result(binding) endend

class Question attr_accessor :title, :answers def initialize title, answers @title = title @answers = answers endend

class Context attr_accessor :title, :questions def initialize title, questions @title = title @questions = questions endend

class Questionnaire TEMPLATE = ... def self.create_html(context) puts ERB.new(TEMPLATE).result(binding) endend

class Question attr_accessor :title, :answers def initialize title, answers @title = title @answers = answers endend

class Context attr_accessor :title, :questions def initialize title, questions @title = title @questions = questions endend

class Questionnaire TEMPLATE = ... def self.create_html(context) puts ERB.new(TEMPLATE).result(binding) endend

class Question attr_accessor :title, :answers def initialize title, answers @title = title @answers = answers endend

class Context attr_accessor :title, :questions def initialize title, questions @title = title @questions = questions endend

class Questionnaire TEMPLATE = ... def self.create_html(context) puts ERB.new(TEMPLATE).result(binding) endend

class Question attr_accessor :title, :answers def initialize title, answers @title = title @answers = answers endend

class Context attr_accessor :title, :questions def initialize title, questions @title = title @questions = questions endend

class Questionnaire TEMPLATE = ... def self.create_html(context) puts ERB.new(TEMPLATE).result(binding) endend

DSLじゃない実現実際に見てみる

✓ プログラマにとっては普通のやり方✓ アンケートを作る人には使いづらい

DSLじゃない実現

✓ これをベースにアンケートを作る人でも使えるようなDSLに

DSLじゃない実現

DSLによる実現

グローバル・メソッドを使って

作戦その1

グローバル・メソッドを使って

DSLのイメージ

context "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

context "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

context "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

context "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

context "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

グローバル・メソッドを使って

DSLの実装

def context text @target = Context.new(text, []) yield puts ERB.new(TEMPLATE).result(binding)end

def question text q = Question.new(text, []) yield q @target.questions << qend

def context text @target = Context.new(text, []) yield puts ERB.new(TEMPLATE).result(binding)end

def question text q = Question.new(text, []) yield q @target.questions << qend

def context text @target = Context.new(text, []) yield puts ERB.new(TEMPLATE).result(binding)end

def question text q = Question.new(text, []) yield q @target.questions << qend

def context text @target = Context.new(text, []) yield puts ERB.new(TEMPLATE).result(binding)end

def question text q = Question.new(text, []) yield q @target.questions << qend

グローバル・メソッドを使って

実際に見てみる

context "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

表現のポイント✓ 問題領域の語彙をメソッド名に✓ ()の省略✓ ブロックの使用

✓ メソッドが増えると管理が困難✓ 小さい規模のDSL向けの戦略

グローバル・メソッドを使って

大きいDSLを作るには?

その前にちょっと寄道

表現の追求

context "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

名前付き引数の導入

名前付き引数の導入

DSLの実装

def context text @target = Context.new(text, []) yield puts ERB.new(TEMPLATE).result(binding)end

def question text q = Question.new(text, []) yield q @target.questions << qend

def context args @target = Context.new(args[:title], []) yield case args[:to] when :html puts ERB.new(TEMPLATE).result(binding) else p @target endend

def question text ...end

def context args @target = Context.new(args[:title], []) yield case args[:to] when :html puts ERB.new(TEMPLATE).result(binding) else p @target endend

def question text ...end

引数にキーでアクセス

表現のポイント✓ シンボルとハッシュで名前付き引数の使用✓ 意図をより明確に表現✓ 全てがオブジェクトであることを利用した表現

context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

表現の追求(その2)

context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do |q| q.answers << "初めて" q.answers << "常連" end question "今回の勉強会はどうでしたか?" do |q| q.answers << "良かった" q.answers << "まあまあ" q.answers << "最低" end question "次回も参加したいですか?" do |q| q.answers << "したい" q.answers << "内容によっては" q.answers << "二度と来ない" endend

冗長なのでなんとかしたい

context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do [ "初めて", "常連" ] end question "今回の勉強会はどうでしたか?" do [ "良かった", "まあまあ", "最低"] end question "次回も参加したいですか?" do [ "したい", "内容によっては", "二度と来ない"] endend

def context args ...end

def question text q = Question.new(text, []) yield q @target.questions << qend

def context args ...end

def question text @target.questions << Question.new(text, yield)end

context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do [ "初めて", "常連" ] end question "今回の勉強会はどうでしたか?" do [ "良かった", "まあまあ", "最低"] end question "次回も参加したいですか?" do [ "したい", "内容によっては", "二度と来ない"] endend

だいぶアンケートらしく書けるようになった

その前にちょっと寄道ed

大きいDSLを作るには?

オブジェクトを使って

作戦その2

オブジェクトを使って

DSLのイメージ

Questionnaire.context :to => :html, :title => "R..." do |q| q.question "勉強会への参加は初めてですか?" do [ "初めて", "常連" ] end q.question "今回の勉強会はどうでしたか?" do [ "良かった", "まあまあ", "最低"] end q.question "次回も参加したいですか?" do [ "したい", "内容によっては", "二度と来ない"] endend

Questionnaire.context :to => :html, :title => "R..." do |q| q.question "勉強会への参加は初めてですか?" do [ "初めて", "常連" ] end q.question "今回の勉強会はどうでしたか?" do [ "良かった", "まあまあ", "最低"] end q.question "次回も参加したいですか?" do [ "したい", "内容によっては", "二度と来ない"] endend

Questionnaire.context :to => :html, :title => "R..." do |q| q.question "勉強会への参加は初めてですか?" do [ "初めて", "常連" ] end q.question "今回の勉強会はどうでしたか?" do [ "良かった", "まあまあ", "最低"] end q.question "次回も参加したいですか?" do [ "したい", "内容によっては", "二度と来ない"] endend

Questionnaire.context :to => :html, :title => "R..." do |q| q.question "勉強会への参加は初めてですか?" do [ "初めて", "常連" ] end q.question "今回の勉強会はどうでしたか?" do [ "良かった", "まあまあ", "最低"] end q.question "次回も参加したいですか?" do [ "したい", "内容によっては", "二度と来ない"] endend

オブジェクトを使って

DSLの実装

def Questionnaire ... def self.context args q = Question.new(args[:title], []) yield self ... end

def self. question text ... endend

Questionnaire.context :to => :html, :title => "Ruby勉強会-8の..." do |q| q.question "勉強会への参加は初めてですか?" do [ "初めて", "常連" ] end q.question "今回の勉強会はどうでしたか?" do [ "良かった", "まあまあ", "最低"] end q.question "次回も参加したいですか?" do [ "したい", "内容によっては", "二度と来ない"] endend

表現のポイント

✓ クラス・メソッドを利用してDSLを実現✓ メソッドを管理しやすい✓ 若干記述が冗長に

メタプログラミングを使って

作戦その3

DSLのイメージ

メタプログラミングを使って

context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do [ "初めて", "常連" ] end question "今回の勉強会はどうでしたか?" do [ "良かった", "まあまあ", "最低"] end question "次回も参加したいですか?" do [ "したい", "内容によっては", "二度と来ない"] endend

オブジェクトを使って

DSLの実装

def method_missing sym, *args case sym when :context @target = Context.new(args[0][:title], []) yield case args[0][:to] when :html puts ERB.new(TEMPLATE).result(binding) else p @target end when :question q = Question.new(arg[0], yield) @target.questions << q endend

def method_missing sym, *args case sym when :context ... when :question ...

def method_missing sym, *args case sym when :context ... when :question ...

def method_missing sym, *args case sym when :context ... when :question ...

when :context @target = Context.new(args[0][:title], []) yield case args[0][:to] when :html ... end when :question q = Question.new(arg[0], yield) @target.questions << q end

def method_missing sym, *args case sym when :context @target = Context.new(args[0][:title], []) yield case args[0][:to] when :html puts ERB.new(TEMPLATE).result(binding) else p @target end when :question q = Question.new(arg[0], yield) @target.questions << q endend

context :to => :html, :title => "Ruby勉強会-8のアンケート" do question "勉強会への参加は初めてですか?" do [ "初めて", "常連" ] end question "今回の勉強会はどうでしたか?" do [ "良かった", "まあまあ", "最低"] end question "次回も参加したいですか?" do [ "したい", "内容によっては", "二度と来ない"] endend

def method_missing sym, *args case sym when :context @target = Context.new(args[0][:title], []) yield case args[0][:to] when :html puts ERB.new(TEMPLATE).result(binding) else p @target end when :question q = Question.new(arg[0], yield) @target.questions << q endend

表現のポイント✓ メソッドを定義せずに動的に機能を実現✓ 利用者の負担は最低限に✓ 拡張も容易

まとめ

✓ ドメインの専門用語を反映し、簡潔な方法でドメインの専門知識を表現するもの

DSL

作戦の立て方

大きな作戦✓ グローバル・メソッド✓ オブジェクト✓ ex) クラス・メソッドの使用

✓ メタプログラミング✓ ex) method_missingの使用

小さな作戦✓ 問題領域の語彙をメソッド名に✓ 省略記法の使用✓ 名前付き引数による表現✓ ブロックによる表現

表現のポイント

表現のポイント✓ 利用者の負担は最低限に✓ 利用者の語彙で、記述は少なく

✓ 意図をより明確に✓ 何をどこに書けば良いかを明らかに

✓ 内部表現も併せて考える✓ 管理の容易性、スコープ

see also

for a good DSL hitchhiking!より良いヒッチハイクを!

ご清聴ありがとうございました

何かご質問は?

top related