Re: Domain-Specific Language (DSL)
Posted on March 26th, 2009 at 15:09 by fr3@K

Keiko 寫了篇 討論 DSL 的文字. 這篇文字不短, 有層次. 有他說的 "學院派式" 的格調. 讓我有一種在看一份探討輪胎直徑與胎壓以及胎壁厚度之間關係的 paper 的 feel. 相形之下, 這裡寫的東西看起來更像是黑手示範如何補胎換胎的 howto.

好了, 細節不多談, 建議看官移架拜讀. 重點是, 本要在 comment 裏寫 code, 但這該死的 blogger 超難用, 讓我想起當年 從 blogger 離家出走 的原因.1 又扯遠了, 回到我想回應給 Keiko 的東西.


文字中一段談到 metaprogramming 可以把部份的 domain-specific 錯誤偵測從 run time 往前拉到 compile time. 文中以這樣的 interface 為例:

    Listing 1

    C++:
    1. template <int RowT, int ColT>
    2. class MyMatrix {
    3. public:
    4.   // !fr3@K! Detail omitted.
    5.   MyMatrix();
    6. };
    7.  
    8. // !fr3@K! This explicit declaration is added to help
    9. //         expressing my point.
    10. template <int RowT, int ColT>
    11. MyMatrix<RowT, ColT> operator +(
    12.   const MyMatrix<RowT, ColT>& lhs,
    13.   const MyMatrix<RowT, ColT>& rhs );

在這樣的 use case 時:

    Listing 2

    C++:
    1. MyMatrix<10, 10> m1;
    2. MyMatrix<2, 2>   m2;
    3.  
    4. cout <<m1 + m2 <<endl;

會產生下面的 compile time error diagnosis (in MSVC 2005):

error C2679: binary '+' : no operator found which takes a right-hand operand of type 'MyMatrix' (or there is no acceptable conversion)

雖然 compiler 已經把錯誤抓到了, 可是錯誤訊息卻不是很友善. Keiko 希望能有 "incompatible dimensions" (domain-specific error) 的提示. Okay, 讓我這個黑手示範一下在這個 use case 下, 怎麼樣叫 compiler 吐出 "incompatible dimensions". operator+ 的宣告要改成類似這樣:

    Listing 3

    C++:
    1. template <
    2.   size_t RowLhs, size_t ColLhs,
    3.   size_t RowRhs, size_t ColRhs>
    4. MyMatrix<RowLhs, ColLhs> operator +(
    5.   const MyMatrix<RowLhs, ColLhs>& lhs,
    6.   const MyMatrix<RowRhs, ColRhs>& rhs);

通常, 好的 interface 會儘量設計成不允許錯誤的使用被 compile, 並且讓錯誤發生的時機愈早愈好. Keiko 舉的 use case 所引發的錯誤就是一個例子, 若用 Keiko 的 interface, 這個錯誤會發生在 operator+ 的 caller site, 也就是 Listing 2 的 line 4. 但我為了讓 error message 能更為明確, 選擇反向操作 - 讓 operator+ 的 caller site 能被 compile, 將錯誤 defer 到實例化 operator+ 的時候發生. 其實作為:

    Listing 4

    C++:
    1. template <
    2.   size_t RowLhs, size_t ColLhs,
    3.   size_t RowRhs, size_t ColRhs>
    4. MyMatrix<RowLhs, ColLhs> operator +(
    5.   const MyMatrix<RowLhs, ColLhs>& lhs,
    6.   const MyMatrix<RowRhs, ColRhs>& rhs)
    7. {
    8.   static_assert(
    9.     RowLhs == RowRhs && ColLhs == ColRhs,
    10.     "incompatible dimensions");
    11.  
    12.   MyMatrix<RowLhs, ColLhs> tmp;
    13.   return tmp += rhs;
    14. }

我手邊沒有 C++0x 的 compiler, 無法確切告訴你 static_assert 實務上會吐出怎樣的錯誤訊息. 但沒意外的話, 應該會是 Keiko 想要的 message.2

沒有 C++0x 的 compiler? 沒關係, 還有 Boost.StaticAssert:

    Listing 5

    C++:
    1. template <
    2.   size_t RowLhs, size_t ColLhs,
    3.   size_t RowRhs, size_t ColRhs>
    4. MyMatrix<RowLhs, ColLhs> operator +(
    5.   const MyMatrix<RowLhs, ColLhs>& lhs,
    6.   const MyMatrix<RowRhs, ColRhs>& rhs)
    7. {
    8.   BOOST_STATIC_ASSERT(
    9.     (RowLhs == RowRhs && ColLhs == ColRhs) &&
    10.     "incompatible dimensions");
    11.  
    12.   MyMatrix<RowLhs, ColLhs> tmp;
    13.   return tmp += rhs;
    14. }

雖然吐出的 error message 沒有包含 "incompatible dimensions", 但會 output 出包含 "boost::STATIC_ASSERTION_FAILURE" 的訊息, 並指出發生 static assertion failure 的是 line 4 of Listing 2 所 instantiate 的 line 8 of Listing 5. 只要 programmer 跑過去看就可以看到在 BOOST_STATIC_ASSERT 這個 statement 中的 "incompatible dimensions" 字串. 有點麻煩, 有點陽春, but it still works, more or less.

如果你跟我一樣覺得 Boost.StaticAssert 這樣的行為有點鳥, 可以試試另外一種 static assertion 的 implementation, 類似下面這樣:

    Listing 6

    C++:
    1. template <bool> struct my_static_assertion;
    2. template <>     struct my_static_assertion<true> {};
    3.  
    4. #define MY_STATIC_ASSERT(exp, message) \
    5. { \
    6.   my_static_assertion<((exp) != 0)> ERROR_##message; \
    7.  (void)ERROR_##message; \
    8. }

並把 Listing 5 的第八行開始的 BOOST_STATIC_ASSERT statement 換成:

    Listing 7

    C++:
    1. MY_STATIC_ASSERT(
    2.   RowLhs == RowRhs && ColLhs == ColRhs,
    3.   incompatible_dimensions);

這時候的 error output 會像下面這樣 (using gcc):

error: ‘ERROR_incompatible_dimensions’ has incomplete type

這個 implementation 有一個很明顯的 limitation, 看 實作 就會知道它要的 message 只能是一個字 (誤). No space, no symbols except underscore (底線/下滑線). 所以在 Listing 7 的例子裏, 用的是 incompatible_dimensions, 而不是更容易讀的 incompatible dimensions.

Have fun!

  1. 搬家的另一個主因是當年 blogger 沒有 tag/label. []
  2. 這裡 有個實例. []
del.icio.us:Re: Domain-Specific Language (DSL) digg:Re: Domain-Specific Language (DSL) spurl:Re: Domain-Specific Language (DSL) newsvine:Re: Domain-Specific Language (DSL) furl:Re: Domain-Specific Language (DSL) Y!:Re: Domain-Specific Language (DSL) 黑米共享書籤:Re: Domain-Specific Language (DSL) 推推王:Re: Domain-Specific Language (DSL)
Previous Post
« Code Review – ActiveMQ-CPP «
Next Post
» [Note] Installing Review-Board on Ubuntu Linux 8.10 »

1 Comment »

Comment #5001

有一種在看一份探討輪胎直徑與胎壓以及胎壁厚度之間關係的 paper 的 feel

哈哈,這句話太好笑了,中午看到你這句,我就爆笑出來了!

真的很感謝大師的補充,的確,我在寫 domain-specific error checking/report 時,只想到了 C++ 可以用 type system 去做 constraint ,卻忘了 metaprogramming 裡頭的 compiler time assertion 技巧可以補足這部份,看來我還不夠融入 C++ metaprogramming 的思維 : (

或許以後這種討論語言性質的文章,我都應該分兩篇,一篇講 general case ,一篇講 C++ !畢竟 C++ 跟 compiler 是好朋友,很多問題,C++ 都可以拼出好幾組有趣的解法 : ) 好吧,那下一篇文章就是: C++ 在 DSL 的應用了!?

Comment by Keiko — March 26, 2009 @ 22:35


Comments RSS TrackBack URI

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>