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
or
$ 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:
#!/usr/bin/snaprotate

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.
#!/bin/bash
FN=$(mktemp /tmp/snaprotateXXXXX).hs
FNHS=$FN.hs
LIBDIR=$(dirname $0)

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

32804384892038493284093

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

4 comments:

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

    ReplyDelete
  2. An easier approach is to use literate haskell.

    This is common in Setup.lhs files for cabal:

    #!/usr/bin/runhaskell
    \begin{code}
    ...
    \end{code}

    or

    #!/usr/bin/runhaskell

    > your dsl
    > goes here

    ReplyDelete
  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..."

    ReplyDelete