TaskScheduler Domain Specific Language converted to Lisp

| | Comments () | TrackBacks (0)

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.

Oren says,

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

Warning: I'm using the Scheme dialect and the MzScheme implementation. I'm using the MzScheme thread library instead of the official multi-threading extension to Scheme.

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 "admin@example.org" "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.

(task "warn if website is not alive"
      every 3 seconds
      starting now
      when (not (website-alive? "http://example.org"))
      then (notify "admin@example.org" "server down!"))

Here is the final implementation:

(define-syntax task
  (syntax-rules (every starting when now then in)
    ((task task-name every time-value time-unit starting in start-value start-unit
	   when test then do-stuff ...)
     ;; The first thread waits
     (thread (lambda ()
	       (sleep (time->seconds start-value start-unit))
	       (thread
		(lambda () (let loop ()
			     (if test
				 (begin do-stuff ...))
			     (sleep (time->seconds time-value time-unit))
			     (loop)))))))
    ((task task-name every time-value time-unit starting now
	   when test then do-stuff ...)
     (thread (lambda () (let loop ()
			  (if test
			      (begin do-stuff ...))
			  (sleep (time->seconds time-value time-unit))
			  (loop)))))))

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 now and when.

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.

Conclusion

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!

0 TrackBacks

Listed below are links to blogs that reference this entry: TaskScheduler Domain Specific Language converted to Lisp.

TrackBack URL for this entry: http://www.neverfriday.com/cgi-bin/mt/mt-tb.cgi/14

Comments

About this Entry

This page contains a single entry by Rudolf Olah published on December 6, 2007 4:51 AM.

Automating Simple Tasks with Scheme (Competing with Perl, Python and Ruby) was the previous entry in this blog.

Validating File Uploads with Django is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Jobs

Powered by Movable Type 4.1