TaskScheduler Domain Specific Language converted to Lisp
Oren Eini has written a DSL that uses the Boo language and does a bit of sub-classing. This was in response to a reply to one of his posts by a blogger named Tim Wilde. The purpose of a domain-specific language is to make it easier to talk about the domain. The DSL written is written for the domain of scheduling tasks. Instead of typing out the following:
myTask = new Task("warn if website not alive", (3 * 60), ... ) myTask.start()
You would type out:
task "warn if website not alive" every 3 minutes starting now ...
It’s more readable for other users and it hides details that are unnecessary when talking about the domain.
Tim came up with a C# variant which had roughly 250 lines of code, 8 classes and 6 interfaces. Over-kill in my opinion. Oren came up with something that makes the DSL look cleaner and it uses the Boo language.
There are several interesting things that are going on in the DSL. For a start, we are using Boo’s ability to drop parenthesizes if needed, which gives us a more “keyword” feeling. Another interesting approach is that when the last parameter of a method is a delegate, we can use just create a block and use that as a way to create the delegate.
The keyword feeling is fantastic. But it can be taken much further. Much much further. Lisp comes with very powerful macros that can be used to write DSLs and DSLs have been written in Lisp since before I was born in 1987. Being able to create a DSL by extending a language is nothing new. This is why I’ve implemented my own version of the above DSL in Lisp
My implementation uses Scheme and the
syntax-rules pattern language. I didn’t much like it but now I am getting used it and may use it more often than the Common Lisp
defmacro method. I have no idea exactly what the differences are since I am Lisp beginner, but I was able to use Scheme to write an implementation of this DSL in less than 30 minutes.
The code consists of 3 functions: 2 are placeholders, the other one converts whatever time is given into the equivalent amount in seconds. So, 1 minute turns into 60 seconds and 3 minutes turns into 180 seconds. This is used with the
sleep function in each task’s thread which controls how long to wait until the task is executed again.
The 2 Patterns Detected
The code also includes 1 syntax definition that has 2 similar patterns that it can match. The 1st pattern is triggered when the task is not starting immediately. An example:
(task "warn if website is not alive" every 3 seconds starting in 5 seconds when (not (website-alive? "http://example.org")) then (notify "email@example.com" "server down!"))
The 2nd pattern is triggered when the task is starting immediately. This is triggered when the keyword “starting” is followed by the word “now”. The above example re-written to start immediately.
2 Interesting Things
2 interesting things that you must know about this code: literals and named let. Literals are using in the
syntax-rules pattern language and are the 1st argument to the
syntax-rules call. Literals allow us to include keywords in the pattern, such as
The other interesting thing is the used of the named let. This is when the 1st argument of a
let call, a literal, is the name of the “function” created. The 2nd argument is the list of parameters which can include default values that the “function” will be called with. A named let is typically used to add a recursive loop in the middle of another function.
The download is available here. The code is very short…59 lines including comments. It uses no classes and no interfaces. Some ideas for improvements include hooking into a task/thread list so that tasks/threads can be killed or retrieved later on.
I’ve blogged about this only to show that Lisp can be used as well and can be more concise than other languages. DSLs are not a new idea but that doesn’t mean I’m not happy that people are finding great uses for them.
Questions, corrections and comments are welcome!