2013年12月25日

DSL - Domain Specific Language 簡介

DSL - Domain Specific Language 簡介

Martin Fowler 針對 DSL 的主題,寫了一本書 Domain Specific Language(有篇文章提供了讀後心得:領域特定語言,被忽視多年的編程利器),另外也有一篇短文 Language Workbenches: The Killer-App for Domain Specific Languages?,根據這篇文章,在 InfoQ 還有一個演講影片 Introduction to Domain Specific Languages

以下以自己的理解,來解釋 Martin Fowler 想要說明的東西。

Language Oriented Programming

DSL 並不一種嶄新的概念,Martin Fowler 認為的新趨勢是 Language Oriented Programming,也就是要用設計一個新的 DSL 的想法,嘗試去做一個更具有彈性的程式實作,他先以一個例子來說明 DSL 的發展過程。

  1. 一開始因為有一份 Evet Data 純文字文件,它有特定的格式,因此產生了撰寫程式處理這個文字資料的需求。

    SVCLFOWLER 10101MS0120050313.........................
    SVCLHOHPE  10201DX0320050315........................
  2. 接下來他用了一個 class diagram,strategy pattern 說明他寫的Java處理程式,基本的想法,就是根據 Data 前面四個字元的識別 keyword,來判斷要使用那一種 strategy,以處理某種特定的Event。

    public void Configure(Reader target) {
     target.AddStrategy(ConfigureServiceCall());
     target.AddStrategy(ConfigureUsage());
    }
    private ReaderStrategy ConfigureServiceCall() {
     ReaderStrategy result = new ReaderStrategy("SVCL", typeof (ServiceCall));
     result.AddFieldExtractor(4, 18, "CustomerName");
     result.AddFieldExtractor(19, 23, "CustomerID");
     result.AddFieldExtractor(24, 27, "CallTypeCode");
     result.AddFieldExtractor(28, 35, "DateOfCallString");
     return result;
    }
  3. 後來他發現,可以將程式抽象化,拆分成兩個部份,就是用固定的程式碼,搭配一個 XML 設定檔。可以用某個 XML Reader 將設定讀進來,然後再對應到上面那個步驟的 strategy class,這樣的好處是,不需要重新編譯程式碼,改寫設定,就可以直接執行。

    <ReaderConfiguration>
     <Mapping Code = "SVCL" TargetClass = "dsl.ServiceCall">
         <Field name = "CustomerName" start = "4" end = "18"/>
         <Field name = "CustomerID" start = "19" end = "23"/>
         <Field name = "CallTypeCode" start = "24" end = "27"/>
         <Field name = "DateOfCallString" start = "28" end = "35"/>
     </Mapping>
    </ReaderConfiguration>
  4. XML 的標籤看起來太礙眼了,可以用另一種更簡潔的語法,來描述這個設定。

    mapping SVCL dsl.ServiceCall
     4-18: CustomerName
     19-23: CustomerID
     24-27: CallTypeCode
     28-35: DateOfCallString
  5. 最後,我們可用另一種自訂的 syntax 語法來改寫,因為要是一種語言,就必須要符合 EBNF 的定義規則。

    mapping('SVCL', ServiceCall) do
     extract 4..18, 'customer_name'
     extract 19..23, 'customer_ID'
     extract 24..27, 'call_type_code'
     extract 28..35, 'date_of_call_string'
    end

Language Oriented Programming 就是一種開發模式,嘗試用創造與使用 DSL 的方式,來完成一個軟體系統。基本上,所有用 DSL 設計的系統,都可以用 GSL 的方式完成。

Internal vs External DSL

接下來,Martin Fowler 說明了 Inernal 跟 External DSL 的差異。

他問了一個問題:以下這個設定的程式碼,是不是一種 DSL?

public void Configure(Reader target) {
    target.AddStrategy(ConfigureServiceCall());
    target.AddStrategy(ConfigureUsage());
}
private ReaderStrategy ConfigureServiceCall() {
    ReaderStrategy result = new ReaderStrategy("SVCL", typeof (ServiceCall));
    result.AddFieldExtractor(4, 18, "CustomerName");
    result.AddFieldExtractor(19, 23, "CustomerID");
    result.AddFieldExtractor(24, 27, "CallTypeCode");
    result.AddFieldExtractor(28, 35, "DateOfCallString");
    return result;
}

答案要根據你在那一種 GSL 上運作,上面這些 code ,如果放到 Ruby,那就是 DSL,但如果在 Java,就不是 DSL,因為 Java 本身並沒有提供任何語言的擴充工具,讓我們可以直接處理這樣的 Configuration Code。

External DSL

  1. 跟運行環境的程式語言完全不同
  2. 需要有 compiler/interpreter 才能運作

缺點:

  1. 缺少IDE
  2. parser/generator 技術太複雜
  3. 自創的語法,產生了很多語言,會出現學習上的困擾及 gap,雖然用Inernal的方式,不需要學很多語言,但還是要學習使用特定的 API

Internal DSL

  1. 以運行環境的程式語言撰寫
  2. 直接使用該程式語言的 syntax

缺點:

  1. 跟運行環境的程式語言緊密結合在一起,例如 因為 ruby 語法的限制 4..18 就不能寫成 4-18
  2. Java, C# 這種 mainstream languages,很難做出 Ineternal DSL,因為當初設計時,就不支援 (不過Java7已經支援 scripting 了)

跳脫 XML,將視野放在 DSL

Martin Fowler 提醒我們,多數的 Java Programmer 會使用 XML,會知道怎麼製作設定檔,但是不知道其實設定檔,也可以再往前推進,形成一種 DSL,下一次當我們面對這麼多繁雜的設定檔時,或許該換個角度想想,是不是能用某種 script,設計出一種領域專用的 DSL 來解決問題。