[Haskell-cafe] Re: A Monad for on-demand file generation?

ChrisK haskell at list.mightyreason.com
Thu Jul 3 14:09:58 EDT 2008

Joachim Breitner wrote:
>  * The 5th line does not have this effect. Because this gets desugared
> to (>>), the special implementation of (>>) means that the next line
> still sees the same dependency state as the before the call to liftIO.

You are violating the monad laws.  (f >> k) and (f >>= \_ -> k) should do the
same thing.  You might write a version of liftIO that has the effect you want,

>  * A change to inFile3 causes outFile1 to be re-written, although from
> looking at the code, _we_ know that this is not necessary, but the ODIO
> monad can not tell. The programmer should have swapped the lines.

Let me reverse engineer your algorithm (aside from the screwy >>):

Every readFile that is encountered in processing ODIO is added to a list of
source files.  The reading deferred to be lazy with unsafePerformIO.

When a writeFile is encountered it is assumed to depend on all previously read
files.  If this output file already exists and is newer than all the source
files, then writing it is skipped (and perhaps also the lazy reads are skipped).
Otherwise, the writing is strict.


I would say this is an unusual module.  I rather prefer Makefile semantics,
which could be improved in some ways by using a DSL in Haskell instead.

The syntactic form of a file-oriented Makefile declaration is

output : input1 input2
   shell script
   more shell script

And the "shell script" has access to the output file name, and also has access
to the input names.

In Haskell you could have a monadic DSL where the output name (and perhaps some
explicit input names) are accessible like MonadReader.

The result of running the DSL would do no IO at all but, much like a compiler,
would return an IO action (the program to create the output file) and a list of
inferred dependencies (an improvement over the Makefile syntax).  Even if the
DSL does not allow liftIO, it can still compile to various IO actions.

Then you have a map from (outputname) to (dependencies,ioAction).  And when
outputname is demanded you can walk the dependencies to see if the timestamps
are newer or older, using the ioActions to create the desired files.

So perhaps to run the DSL monad you have a function like:

makeRule :: DSL () -> FilePath -> [FilePath] -> ( [FilePath], IO () )

type Depends = Map FilePath ([FilePath], IO ())

demand :: Depends -> FilePath -> Maybe ByteString

More information about the Haskell-Cafe mailing list