More on Haskell DSL

In an earlier post, partial make replacement, I developed a small Haskell based DSL to express GNU make like rules. In this post, I take the idea further to support multiple dependencies per target.

testO = (return ["test.c", "test.h"]) ->> ccompile >>- "test.o"

testExe = testO ->> link >>- "test.exe"

main = testExe

The above example, expresses in a simple way, that test.o depends on test.c and test.h and can be "made" using ccompile (C compilation). Similarly, test.exe depends on test.o and can be generated by linking test.o. Checkout the Hmake module (30 lines, including the definition of ccompile and link) and try it out yourself. It now properly checks if a target is out of date with its dependency before trying to build to the target.

Compare the above with the elaborate topological sort based ermake implementation (232 lines) using erlang.

With the Haskell module above, one can even build multiple targets in parallel by using forkIO (part of the Concurrent Haskell).

Another DSL embedded in Haskell - a partial make replacement in less than 10 lines

My earlier post on Haskell described a small DSL for calendar apps. In this post, I will show a very simple DSL (written in just 6 lines of code) to replace (a small portion of) GNU make utility. Earlier, I had attempted the GNU make replacement in Haskell without the use of DSL concepts.

At a very basic level, GNU make allows one to describe how a particular goal (or target) is made (generated using a set of shell commands) from a set of its dependencies. In Haskell, I should be able to describe this relationship like this:

return "test.c" ->> ccompile >>- "test.o" ->> link >>- "test.exe"

In other words, test.exe is made from test.o, which in turn is made from test.c.
The plumbing code can be written in a few of lines like this:

import System.Cmd

dep ->> cmd = \target -> dep >>= (\xdep -> (cmd xdep target) >> return target)

tcmd >>- target = (tcmd target) >> return target

ccompile dep target = system("gcc -c " ++ dep ++ " -o " ++ target)

link dep target = system("gcc  " ++ dep ++ " -o " ++ target)

Notice, how the dataflow is made clear because of the notations used. In addition, the use of IO monad means you can keep on "linking" output of one stage to another. Compare and contrast this with the compiler based DSL using Ocaml described here.
For the sake of simplicity, I haven't added the code to check if the target is out of order with   
respect to its dependency. And only one dependency per target is currently supported. These features however, are not very hard to add. As usual, your feedback is highly welcome and is sincerely appreciated. You can download the code from here.

Haskell, DSL and Monad

Haskell is an amazing language. One can easily embed a DSL (domain specific language) using monad. Let's take an concrete example to illustrate the power of DSL and Monads in Haskell.

I was always fascinated by a very useful, and nifty program, called remind. It is a very sophisticated calendering program that allows one to set reminders. For example, "REM 4 July MSG It's Independence Day!", will trigger an reminder on every July 4th. However, this kind of simple reminders can be set using any calendering application. 'Remind' provides a very flexible date specification for setting reminders. The date spec consists of 'day month year weekday'. If you omit the date spec, the reminder occurs every day. If you specify only the day part, then the reminder is triggered on the specified day of every month. If only month is specified, then the reminder is triggered every day of the specified month. And so on... You can take a look at the man page of the remind for more details about the date spec rules.

Remembering the above rules can be tricky. Instead, let's see if Haskell can help us here. I used an idea proposed by Ketil Malde on Haskell-Cafe mailing list on how to handle calendar dates in Haskell. Here is the basic idea:

data Year = Y Int

data MonthEnum = January | February | March | April | May | June | July | August
                    | September | October | November | December   deriving (Show, Eq, Enum, Bounded, Ord)

data DayEnum = Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday   deriving (Show, Eq, Enum, Bounded)

data Month = M MonthEnum Year
data Day   = Dm Int Month

Haskell uses lazy evaluation by default. This makes it easier to represent an infinite stream of years starting from 2007 as:

years = [Y y | y <- [2007..]]

Using similar list comprehension notation, list of months in a given year and list of days in a given months can be represented as,

months (Y y) = [M m (Y y) | m <- [January .. December]]

days (M m yy@(Y y))
    | m `elem` [January,March,May,July,August,October,December] = [Dm d (M m yy) | d <- [1..31]]
    | m == February = [Dm d (M m yy) | d <- [1..if y `mod` 4 == 0 then 29 else 28]]
    | otherwise = [Dm d (M m yy) | d <- [1..30]]

Notice how leap years are handled in producing days in a given month and year.

The above code was taken from the Ketil Malde's message at Haskell-Cafe and converted to add Enums.
I added some boilerplate code to display the months properly by implementing an instance of the type class, Show:

instance Show Month where
    show (M m t) = show m ++ " " ++ show t

instance Show Year where
    show (Y y) = " " ++ show y

instance Show Day where
    show (Dm d t) = " " ++ show d ++ " " ++ show t

With the above addition, and using the fact that List in Haskell is a monad, we can easily represent the following,

-- all days of all months of all years (only the first 3 items are shown)
*DateStream> take 3 (years >>= months >>= days)
[ 1 January  2007, 2 January  2007, 3 January  2007]

-- first day of every month of every year
*DateStream> take 3 (years >>= months >>= return.(Dm 1))
[ 1 January  2007, 1 February  2007, 1 March  2007]

-- all days of january of every year
*DateStream> take 3 (years >>= return.(M January) >>= days)
[ 1 January  2007, 2 January  2007, 3 January  2007]

Notice, how easy it is to express the list of days that you are interested in. You can even filter the days by using the 'filter' function provided in Haskell for lists:

-- all mondays in June of any year starting from 2007
*DateStream> take 6 (filter (isDayOfWeek Monday) (years >>= return.(M June) >>=
[ 4 June  2007, 11 June  2007, 18 June  2007, 25 June  2007, 2 June  2008, 9 Jun
e  2008]

'isDayOfWeek' function requires calculating the 'day of week'.

All this functionality in just 43 lines of code (including comments). This already implements all combinations of Remind's date spec that does not involve day of week. It shouldn't be hard to implement the same for day of week.

Most importantly, the user only needs to use simple keywords, such as, years, months, days, january and combine them using the monadic bind operator, '>>='. There, you have a small DSL for calendar apps. You can browse the file here.

Feedback, corrections and suggestions are most welcome.

Update: As some  readers pointed out the leap year calculation were wrong.  Thanks for the feedback. I have corrected the code to use the gregorianMonthLength function from the Data.Time.Calendar module.