07 May, 2010

making haskell EDSLs shebangable

There's a mindset that says to solve your problem, first write a language to solve your problem in, and then solve your problem in that language. And there's a mindset that says that your language should be an embedded language inside Haskell. In many ways that is appealing.

I wanted to write something that would delete old rsync snapshots. I took the domain-specific language approach, where the language specifies the retention policy for snapshots, like this:
policy = recent <||> weekly <||> monthly
meanings the final policy is to keep 'recent', 'weekly' and 'monthly' backups, with other statements defining what those three other policies mean. I won't write more about the language here - there's more on the webpage for the tool, snaprotate.

One particular piece of this I don't feel terribly comfortable with, and thats how to invoke/run the policy files.

I can imagine two different interfaces:
$ snaprotate -f policyfile
$ policyfile

I took the second approach, making policies first order unix executables - commands that can be run like any other command.

In unix tradition the way you indicate the language of a script is with a shebang line (#!/something) at the start of the script.

So then I want my scripts to look like something like this:

policy = recent <||> weekly <||> monthly

This is almost a valid Haskell program, but: i) I need to import the SnapRotate module, and ii) I don't want to specify a main routine (which looks something like:
main = runPolicy policy

So the snaprotate commandline looks almost like runhaskell but does some source file rearranging to address the above two points, and to remove the #! shebang line.
FN=$(mktemp /tmp/snaprotateXXXXX).hs
LIBDIR=$(dirname $0)

cat > $FNHS << 32804384892038493284093
import SnapRotate
main = runLevels policy


cat $1 | grep --invert-match '^#!' >> $FNHS
runhaskell -i$LIBDIR $FNHS $@
I wonder if this is the best way to implement such an embedding?


  1. It is clearly suboptimal. Your EOF marker of 32804384892038493284093 is not prime.

  2. An easier approach is to use literate haskell.

    This is common in Setup.lhs files for cabal:




    > your dsl
    > goes here

  3. From a language-user perspective, I think thats worse, although I can see its easier to implement from the language-implementor side because its using existing mechanisms.

    Its worse because its more boiler plate that doesn't relate directly to the language, and doesn't add anything: "you must put a > at the start of every line.." "so why don't I write a little program to put a > at the start of every line for me?" "umm..."