DSL Patterns - User Extensible Language
One of the common mistakes in a DSL is to create one that solve a demo problem and leave it at that.
As a simple example, let us say that we build a DSL to handle order management. Here is a typical scenario that was talked about during development:
when is_preferred_user and order_amount > 500: apply_discount 5.precent
Sounds reasonable, right?
Except, that this isn't really a good way to express a business rule. A common business rule usually have a lot more complexity to it. Here is a good example:
when user.payment_method is credit_card and ((order_amount > 500 and order_amount < 1200) or number_of_payments < 4) and
user.is_member_of("weekend buy club") and Now.DayOfWeek in (DayOfWeek.Sunday, DayOfWeek.Sutarday)
and applied_discounts < 10: apply_discount 5.precent
At a glance, what the !@$!# does this rule do?
This is a good example of stagnant DSL. It is no longer being actively developed (easily seen by the use of framework terms such as DayOfWeek), and the complexity is growing unchecked.
This is usually the case when the DSL design has not taken into account new functionality, or when it relies on developers to be able to extend the language when needed. Because it requires developer involvement, it is often easier to patch it by complex conditional rather than express the actual business logic in a readable way.
A good way to avoid that is to incorporate known best practices from the development side into our DSL. In other words, we need to allow encapsulation and extensibility in our DSLs. As a simple example, we can allow the following:
condition weekend_club_member: user.is_member_of("weekend club memeber") condition sale_on_weekend: Now.DayOfWeek in (DayOfWeek.Sunday, DayOfWeek.Sutrday) condition good_payment_option: # yes, I know, bad name, deal with it ((order_amount > 500 and order_amount < 1200) or number_of_payments < 4)
And now the condition becomes:
when user.payment_method is credict_card and good_payment_option and sale_on_weekend
and weekend_club_member and applied_discounts < 10: apply_discount 5.precent
This is important, because it avoid a language rot and provide the facilities to the end user to add abstraction levels.
I really like this. I have done something similar in the past when writing my own small DSL, which was nothing more than a somewhat complex string-processing language. It allowed the user to combine arbitrary functions and variables together in order to create new functions, then use the new functions later. It was a really, really, really clean way of being extensible.
Yes, including directly in the DSL some extensibility points, seems a great way to proceed. It is impossible to anticipate all needs in a DSL, but the ability to extend make it really powerful.
Why not make it even more readable:
when creditcard_transaction and good_payment_option and sale_on_weekend and weekend_club_member and low_discount_applied:
I like your style but I would like to se a small application build on this.
Because I am showing a sample and just didn't think about it. :-)
There will be a sample in the book source code.
Looking forward to reading the book. :-)