せかいや

いまいるここを、おもしろく http://sekai-in-the-box.appspot.com/

【Ruby】言語内DSLとは。

Railsチュートリアルを勉強しています。
すごくわかりやすい。楽しい。
 
この中ででてくるRSpec

require 'spec_helper'
describe User do
  pending "add some examples to (or delete) #{__FILE__}"
end

普通だったらメソッドの定義は def だよね?
describe?
あ、これが言語内DSLというやつか!!??

調べてみるとやっぱりそう。
RSpecはDSLなんだ!

調べてみると「るびま」の記事が見つかった。
スはスペックのス 【第 1 回】 RSpec の概要と、RSpec on Rails (モデル編)

RSpec はプログラムの振舞を記述する言語として、Ruby を拡張することを選びま した。このような、DSL を定義する言語と DSL を実行する言語 (ホスト言語) とが 同じである DSL の実装アプローチを「言語内 (internal) DSL」と呼びます。

なるほど!

RSpec が「振舞定義用の DSL」を提供して、プログラマに Test::Unit とは異なる 書き方をさせているのは、ある考え方を私たちに伝えるためです。その考え方とは、 テスト駆動開発 (Test Driven Development:TDD) です。

なるほど!
常々思うけど言語って「思想」だよね。

 

2 行目の自動生成されたメッセージは RSpec ならではの機能です。メッセージは、 describe メソッドや it メソッドに渡したクラス定数や文字列を利用して作成し ています。スペックを定義する場合には、この失敗メッセージを、失敗したテ ストの内容を表すように構成するのがコツです。

うーん、、
まだ慣れてないからかもしれないけれど、
この独特のエラーメッセージ、
あまりにセンテンスになりすぎていて好きになれない。

Array when initialized with object should not affect others' FAILED

こういうメッセージ。

まあでも、Junitとかプロジェクトで使うと、
無意味なコメント(そして整合性が取れず嘘のコメントになる)
を山ほど書く、人/チーム/会社は必ずあるから、
そういう意味では、防止策といえるかな。


 

スペックファイルは Ruby スクリプトですから、振舞の記述に必要なヘルパメ ソッド、クラスやモジュールも通常の Ruby スクリプトと同様、自由に定義できます。 共通の処理をまとめたり、可読性を高めるために積極的に利用するとよいでしょう。

ふんふん。言語内DSL!

it "#empty? は true であること" do...

「日本語で書い たら "it" が意味不明でどうしても気持ち悪い」と思われる方は、 互換性のために残されている旧来の API を使うこともできます。

うーん、、分かった。日本語は使わないようにする。
でも「英語として意味が通じるか」を考えながらプログラミングするって、あまり本質的ではない気がする。

チュートリアルでも、itが不自然な場合はspecifyメソッドを使ってテストを書いているけど、

「it should not equal wrong user」(itはユーザーなど) とするのは英語として自然ですが、「user: user with invalid password should be false」は不自然であり、「specify: user with invalid password should be false」とすれば自然になります。

って、なんだか判断が難しい気がする。
「人間の言葉そのままにプログラミングする」って
たぶん大きな一分野だと思うれど。
「it」って何?を考えなきゃ書けない。


もやもやっとしてた「言語内DSL」が分かったぞ!
Gemfile もシンボルが使えるし、多分言語内DSLだ。

そんなに複雑なものでもない。
ホスト言語がRubyという点では
このあいだ考えた"せかい言語"と変わりはないと思う。


一方、schema.rbは
一見DSLぽくみえる(設定ファイルのように見えて、Rubyを拡張しているように見える)けれどもそうじゃない。

Schema.defineメソッドを実行しているだけ。
doブロックのおかげで表現が豊かになってる。
レシーバーを省略しているから独自の拡張ぽくみえるけど
create_table なんかも単なるメソッド実行。

ActiveRecord::Schema.define(:version => 20130907****) do
  create_table "users", :force => true do |t|
    t.string   "name"
    t.string   "email"
  end
  add_index "users", ["email"], :name => "index_users_on_email", :unique => true
end

doブロックって面白い。