DSL và Lisp
Software Engineering
38
White

Ngoc Dao viết ngày 21/03/2016

Grant Rettke hỏi:

What is all the fuss about how you can write DSLs in Lisp? Everyone from thought-leaders to blog-posters to grandma's are talking about how Lisp is so great for DSLs. About what are these people talking about? Because no one of said people actually elaborate on any of this, of course, which leads me to question their claims.

That said, when I think of a DSL think about letting folks write "programs" like:

trade 100 shares(x) when (time < 20:00) and timingisright()

When I think about syntax transformation in Lisp I think primarily about language features.

Tại sao dạo này người ta nhặng xị lên là viết DSL (ngôn ngữ được chuyên biệt hoá dành cho miền vấn đề chuyên biệt) bằng Lisp rất dễ? Vì chẳng ai giải thích rõ ràng nên tôi rất thắc mắc.

Ví dụ, có "chương trình":

giao_dịch 100 cổ_phiếu(x) khi (giờ < 20:00) và đúng_lúc()

Tôi muốn biết tính năng biến đổi cú pháp của Lisp giúp gì được.

Joe Marshall, cựu sinh viên MIT, trả lời...

In order to talk about domain-specific languages you need a definition of what a language is. Semi-formally, a computer language is a system of syntax and semantics that let you describe a computational process. It is characterised by these features:

  1. Primitive constructs, provided ab-initio in the language.
  2. Means of combination to create complex elements from the primitives.
  3. Means of abstraction to control the resulting complexity.

Trước khi bàn về DSL anh cần định nghĩa thế nào là một ngôn ngữ. Một cách bán chính thức, một ngôn ngữ là một hệ thống cú pháp cho phép miêu tả một quá trình tính toán. Nó có:

  1. Các phần tử nguyên thủy (tương tự như tiên đề trong toán học).
  2. Cách kết hợp các phần tử nguyên thủy ở 1 để tạo ra phần tử phức tạp hơn.
  3. Cách trừu tượng để đơn giản hoá sự phức tạp sinh ra ở 2.

So a domain-specific language would have primitives that are specific to the domain in question, means of combination that may model the natural combinations in the domain, and means of abstraction that model the natural abstractions in the domain.

Như vậy một DSL sẽ có những phần tử nguyên thủy chuyên biệt cho miền vấn đề cần xem xét, có những cách kết hợp để mô hình hoá nó một cách tự nhiên, và có những cách trừu tượng hoá nó một cách tự nhiên.

To bring this back down to earth, let's consider your `trading language':

trade 100 shares(x) when (time < 20:00) and timingisright()

Cụ thể, hãy xét "ngôn ngữ chuyên biệt dành cho giao dịch chứng khoán":

giao_dịch 100 cổ_phiếu_loại(x) khi (giờ < 20:00) và đúng_lúc()

One of the first things I notice about this is that it has some special syntax. There are three approaches to designing programming language syntax. The first is to develop a good understanding of programming language grammars and parsers and then carefully construct a grammar that can be parsed by an LALR(1), or LL(k) parser. The second approach is to `wing it' and make up some ad-hoc syntax involving curly braces, semicolons, and other random punctuation. The third approach is to `punt' and use the parser at hand.

Dễ thấy là nó có cú pháp đặc biệt. Có 3 cách tiếp cận để thiết kế cú pháp ngôn ngữ lập trình. Cách đầu tiên là đâm đầu học khoá về ngữ pháp ngôn ngữ lập trình và trình biên dịch, sau đó lọ mọ xây dựng ngữ pháp sao cho có thể phân tích bằng parser kiểu LALR(1) hoặc LL(k), nghĩa là tự thiết kế cú pháp và tự viết luôn parser. Cách thứ hai là dùng cú pháp có sẵn như ngoặc xoăn { }, dấu chấm phẩy v.v. hoành tráng của những ngôn ngữ "chuẩn". Cách thứ ba là dùng parser có sẵn.

I'm guessing that you took approach number two. That's fine because you aren't actually proposing a language, but rather you are creating a topic of discussion.

Tôi đoán anh chọn cách tiếp cận thứ hai. Cũng tốt vì anh thật ra không định đề ra ngôn ngữ mới, mà chỉ đưa ra ví dụ để thảo luận.

I am continually amazed at the fact that many so-called language designers opt for option 2. This leads to strange and bizarre syntax that can be very hard to parse and has ambiguities, `holes' (constructs that *ought* to be expressable but the logical syntax does something different), and `nonsense' (constructs that *are* parseable, but have no logical meaning). Languages such as C++ and Python have these sorts of problems.

Tôi liên tục ngạc nhiên vì thực tế là nhiều người được gọi là nhà thiết kế ngôn ngữ chọn cách 2. Việc này thường tạo ra cú pháp kì quặc rất khó phân tích, không rõ ràng, có nhiều lỗ hổng (viết với ý thế này hoá ra lại mang nghĩa khác), vô nghĩa (đúng cú pháp nhưng lại chẳng mang ý nghĩa gì). Những ngôn ngữ như C++ và Python có những vấn đề kiểu này.

Few people choose option 1 for a domain-specific language. It hardly seems worth the effort for a `tiny' language.

Ít ai chọn cách 1, vì nếu chỉ để dành cho ngôn ngữ làm mỗi một nhiệm vụ chuyên biệt thì cách này giống dùng dao mổ trâu để giết gà.

Unfortunately, few people choose option 3. Part of the reason is that the parser for the implementation language is not usually separable from the compiler.

Rất tiếc, ít người chọn cách 3. Một phần lí do là parser thường không tách biệt khỏi compiler.

For Lisp and Scheme hackers, though, option 3 is a no-brainer. You call `read' and you get back something very close to the AST for your domain specific language. The `drawback' is that your DSL will have a fully parenthesized prefix syntax. Of course Lisp and Scheme hackers don't consider this a drawback at all.

Tuy nhiên lập trình viên Lisp và Scheme thì chọn cách 3 không cần suy nghĩ. Anh có thể "đọc" một phát là được ngay cấu trúc rất gần với AST của DSL anh cần. Chỉ có điều DSL sẽ gồm toàn ngoặc tròn ( ). Tất nhiên lập trình viên Lisp và Scheme chẳng thấy trở ngại gì với các ngoặc tròn này.

So let's change your DSL slightly to make life easier for us:

(when (and (< time 20:00)
(timing-is-right))
(trade (make-shares 100 x)))

Nào hãy sửa DSL của anh lại một chút:

(khi (và (< giờ 20:00)
(đúng-lúc))
(giao-dịch (tạo-cổ-phiếu 100 x)))

When implementing a DSL, you have several strategies: you could write an interpreter for it, you could compile it to machine code, or you could compile it to a different high-level language. Compiling to machine code is unattractive because it is hard to debug, you have to be concerned with linking, binary formats, stack layouts, etc. etc.

Khi tạo DSL, anh có vài chiến lược: anh có thể viết trình thông dịch cho nó, anh có thể dịch nó thành mã máy, hoặc anh có thể dịch nó thành ngôn ngữ cấp cao khác. Dịch thành mã máy không có gì hấp dẫn vì khó debug, anh phải đau đầu với linker, các định dạng nhị phân, bố trí stack v.v

Interpretation is a popular choice, but there is an interesting drawback. Almost all DSLs have generic language features. You probably want integers and strings and vectors. You probably want subroutines and variables. A good chunk of your interpreter will be implementing these generic features and only a small part will be doing the special DSL stuff.

Thông dịch là lựa chọn phổ biến, nhưng có trở ngại thú vị. Hầu hết các DSL đều có những điểm chung. Ví dụ số nguyên, chuỗi, mảng, hàm, biến. Khi viết trình thông dịch anh phải dành phần lớn thời gian xử lí những điểm chung này, chỉ một phần nhỏ thời gian thực sự xử lí những thứ liên quan đến DSL.

Compiling to a different high-level language has a number of advantages. It is easier to read and debug the compiled code, you can make the `primitives' in your DSL be rather high-level constructs in your target language.

Dịch sang ngôn ngữ cấp cao khác có một số lợi điểm. Sẽ dễ đọc và debug code hơn, anh có thể biến những "phần tử nguyên thủy" trong DSL của anh thành phần tử phức tạp hơn ở ngôn ngữ cấp cao kia.

Lisp has a leg up on this process. You can compile your DSL into Lisp and dynamically link it to the running Lisp image. You can `steal' the bulk of the generic part of your DSL from the existing Lisp: DSL variables become Lisp variables. DSL expressions become Lisp expressions where possible.  Your `compiler' is mostly the identity function, and a handful of macros cover the rest.

Lisp đã làm sẵn giúp một công đoạn trong qui trình này. Anh có thể dịch DSL thành Lisp và xài ké các thư viện của Lisp. Những điểm chung trình bày ở trên có thể xài ké sẵn Lisp: biết của DSL trở thành biến của Lisp. Cú pháp của DSL trở thành cú pháp của Lisp. "Trình biên dịch" của anh chỉ còn là một vài hàm và macro.

You can use the means of combination and means of abstraction as provided by Lisp. This saves you a lot of work in designing the language, and it saves the user a lot of work in learning your language (*if* he already knows lisp, that is).

Anh có thể dùng các cách kết hợp và trừu tượng Lisp cung cấp sẵn. Nhờ đó anh tiết kiệm được rất nhiều công sức khi thiết kết DSL, người khác thì tiết kiệm được rất nhiều công sức khi học DSL của anh, vì cú pháp của nó cũng chỉ là Lisp.

The real `lightweight' DSLs in Lisp look just like Lisp. They *are* Lisp. The `middleweight' DSLs do a bit more heavy processing in the macro expansion phase, but are *mostly* lisp. `Heavyweight' DSLs, where the language semantics are quite different from lisp, can nonetheless benefit from Lisp syntax. They'll *look* like Lisp, but act a bit differently (FrTime is a good example).

DSL "hạng nhẹ" viết bằng Lisp đều trong giống Lisp. Vì chúng chính là Lisp. DSL "hạng trung" dùng macro nặng hơn, nhưng cũng là Lisp. DSL "hạng nặng", nghĩa là cú pháp trong khác Lisp, cũng có thể lợi dụng cú pháp Lisp. Chúng "trông" như Lisp, nhưng hoạt đông hơi khác (FrTime là một ví dụ).

You might even say that nearly all Lisp programs are `flyweight' DSLs.

Thậm chí anh có thể bảo rằng gần như tất cả chương trình Lisp đều là DSL "hạng nhẹ".

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Ngoc Dao

102 bài viết.
300 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
66 8
Làm thế nào để nâng cấp trang web mà không làm gián đoạn dịch vụ? Đây là câu hỏi phỏng vấn các công ty lớn thường hỏi khi bạn xin vào vị trí làm lậ...
Ngoc Dao viết hơn 2 năm trước
66 8
White
42 1
Bài viết này giải thích sự khác khác nhau giữa hai ngành khoa học máy tính (computer science) và kĩ thuật phần mềm (software engineering), hi vọng ...
Ngoc Dao viết hơn 2 năm trước
42 1
White
38 2
Nếu là team leader, giám đốc công ty hay tướng chỉ huy quân đội, vấn đề cơ bản bạn gặp phải là “hướng mọi người đi theo con đường bạn chỉ ra”. Thử...
Ngoc Dao viết hơn 2 năm trước
38 2
Bài viết liên quan
White
1 1
Lập trình đôi (pair programming) là hình thức lập trình trong đó 2 người cùng hợp tác làm việc trên cùng màn hình (có thể khác bàn phím v.v.). Bài ...
Ngoc Dao viết hơn 2 năm trước
1 1
White
29 4
Như thường lệ, là chuyên mục quảng cáo, bài viết được đăng lại từ https://thefullsnack.com/posts/frameworkorlibrary.html Hôm nay mình nghe podca...
Huy Trần viết 27 ngày trước
29 4
White
7 1
Trong quyển sách Beyond Java, xuất bản vài năm trước có đoạn:Java has characteristics that many of us take for granted. You can find good Java deve...
Ngoc Dao viết hơn 2 năm trước
7 1
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
102 bài viết.
300 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!