Specs2のString Interpolationを使った新しいDSL

もうすぐ、specs2がversion2というちょっとややこしい(?)メジャーversionになって出るらしいです。でました。
新機能いっぱいあって自分も把握できてないんですが、その中でも面白いDSLが新機能として入っていたので、それを簡単に紹介します。
英語読める人は、本人がblog書いてるのでこれ http://etorreborre.blogspot.jp/2013/05/the-latest-release-of-specs2-2.html 読みましょう、今回紹介するもの以外にも、網羅的に説明されてます


まず、String InterpolationのDSLを説明するまえに、ちょっとspecs2の基本的な形式について説明します。
specs2にはimmutable*1とmutable*2という2つの形式があります。*3
公式のドキュメントからそのまま拝借しますが、
mutableは

class HelloWorldSpec extends Specification {
  "The 'Hello world' string" should {
    "contain 11 characters" in {
      "Hello world" must have size(11)
    }
    "start with 'Hello'" in {
      "Hello world" must startWith("Hello")
    }
    "end with 'world'" in {
      "Hello world" must endWith("world")
    }
  }
}

というので、immutableは

class HelloWorldSpec extends Specification { def is =

  "This is a specification to check the 'Hello world' string"   ^
                                                                p^
  "The 'Hello world' string should"                             ^
    "contain 11 characters"                                     ! e1^
    "start with 'Hello'"                                        ! e2^
    "end with 'world'"                                          ! e3^
                                                                end
    
  def e1 = "Hello world" must have size(11)
  def e2 = "Hello world" must startWith("Hello")
  def e3 = "Hello world" must endWith("world")
}

という形式です。immutableのほうは、式の戻り値のみをテスト結果とするので(immutableで関数型という意味では)わかりやすいですが、制限が厳しすぎて*4それほど好まれていない気がします。
*5 ! などはimplicit conversionでやってます。


さて上記のimmutableの場合、上の方の部分は「テストの名前や説明の文字列がほとんどで、そこの合間でテストを表すメソッドを呼んでいる」かたちです。そもそもこの時点で、implicit conversionを多用していて普通のプログラムの見た目とは全然違います。良くも悪くも、徹底的に内部DSLの形式を追求してる感じです。implicit conversionのような黒魔術的な機能が言語自体にないと*6、こういうのは書けないですね。


ちなみ、テストの説明が文字列で長く記載されているため、メソッド名はe1、e2という機械的な命名になってます。*7
実はdef e1の部分をe1 :=と書ける*8というものもありますが、脱線しすぎるので省略します。気になる人は本家のドキュメント見てください。



さて、上記のDSLの形式を押しすすめた(?)のが、2.10のString Interpolationを使った新しいものです。こんな感じ↓

Scala2.10から入ったString Interpolationは、拡張できます。たとえばscalikejdbcで、sqlとかsqlsとか使われてますね

https://github.com/seratch/scalikejdbc/blob/1.6.3/scalikejdbc-interpolation-core/src/main/scala/scalikejdbc/SQLInterpolationString.scala#L28-L42

同じように、specs2の2から、s2という独自のInterpolationを定義してるらしいです。

https://github.com/etorreborre/specs2/blob/SPECS2-2.0-RC2/src/main/scala/org/specs2/specification/SpecificationStringContext.scala#L87

しかもその先の実装はmacroになっています。あと、そもそも(String Interpolation使わない形式でも同じですが)わざわざdef e1やdef e2とわけなくてもいいので、以下のように、

「テストコードのほぼすべての部分が、s2のString Interpolationで囲われた大きな一つの式」という、面白い書き方もできます。

これを早速eed3si9nさんがscopt3のテストでつかってました。90行ぐらい連続してs2のString Interpolationで囲われた一つの式になってます、おぉ・・・

https://github.com/scopt/scopt/blob/3.0.0/src/test/scala/scopt/ImmutableParserSpec.scala#L6-L96

そもそも、immutableのスタイル嫌いがでmutableしか使ってない人は、これも使わない気がしますが、個人的には ^ や ! で implicit conversionでつなげるよりは、String Interpolationで全部文字列でつなげたこの形式のほうが、わかりやすい気はします。*9ただ、エディタのシンタックスハイライトが貧弱だったりするとわかりずらいので、IDEを使わざるを得なかったり、github上でもどの部分が式なのかちょっと判別しにくいという欠点はありますが・・・。*10あと「面白そうなので使いたい!」というよりは、macroも結構使っていて内部実装が面白そうなので、そのうち実装を読んでみたいですね。


さて、最初にも書きましたが、あくまでも紹介したのはspecs2の2の機能のなかでもごく一部で、ほかにも色々あるので試してみればいいんじゃないでしょうか(そして誰かblog書いてください・・・)


sbtも0.13から結構macro使いますし、こういう2.10のstring Interpolationやmacroなどの新機能を使ったDSLが、今後も増えていくのでしょうか・・・。

*1:もしくはacceptance形式と呼ぶ

*2:もしくはunit形式と呼ぶ

*3:ほかにも説明しきれないほど、色々書き方ありますが・・・

*4:つまり1つのメソッドにつき1つしかテスト書けない。複数書くならandで繋げないといけない

*5:これに限らないですが

*6:Rubyとかは同じようにできるというか、やってるでしょう。他は知りません

*7:これが推奨?

*8:e1というオブジェクトを事前にspecs2側で定義しておいて https://github.com/etorreborre/specs2/blob/SPECS2-2.0-RC2/src/main/scala/org/specs2/specification/Groups.scala#L308-L329 それのメソッド呼び出しにすることにより、defがいらなくなる。あとそもそもgroupingの機能と関連してたはず

*9:まだほとんど使ってないのでよくわかってないですが、endとか ^ とか色々記号を書かなくて済むし、インデントによって勝手に解析してやってくれるみたい?

*10:あとはバグっていたりした場合、マクロなのでデバックがとてもしんどいとか