This question is related to this article
The idea is to define a DSL for manipulating files in the cloud, and define a composition of interpreters that take care of the different aspects, such as communication with the REST interface and logging.
To make this more concrete, assume we have the following data structure that defines the terms of the DSL.
data CloudFilesF a
= SaveFile Path Bytes a
| ListFiles Path ([Path] -> a)
deriving Functor
We define functions to build CloudFiles programs as follows:
saveFile :: Path -> Bytes -> Free CloudFilesF ()
saveFile path bytes = liftF $ SaveFile path bytes ()
listFiles :: Path -> Free CloudFilesF [Path]
listFiles path = liftF $ ListFiles path id
Then the idea is to interpret this in terms of two other DSL's:
data RestF a = Get Path (Bytes -> a)
| Put Path Bytes (Bytes -> a)
deriving Functor
data Level = Debug | Info | Warning | Error deriving Show
data LogF a = Log Level String a deriving Functor
I managed to define a natural transformation from the CloudFiles DSL to the REST DSL with the following type:
interpretCloudWithRest :: CloudFilesF a -> Free RestF a
Then given a program of the form:
sampleCloudFilesProgram :: Free CloudFilesF ()
sampleCloudFilesProgram = do
saveFile "/myfolder/pepino" "verde"
saveFile "/myfolder/tomate" "rojo"
_ <- listFiles "/myfolder"
return ()
It is possible to interpret the program using REST calls as follows:
runSampleCloudProgram =
interpretRest $ foldFree interpretCloudWithRest sampleCloudFilesProgram
The problem comes when trying to define an interpretation of the DSL using logging. In the article I referred above, the author defines an interpreter with type:
logCloudFilesI :: forall a. CloudFilesF a -> Free LogF ()
and we define an interpreter for Free LogF a
having type:
interpretLog :: Free LogF a -> IO ()
The problem is that this interpreter cannot be used in combination with
foldFree
as I did above. So the question is how to interpret a program in
Free CloudFilesF a
using the function logCloudfilesI
and interpretLog
defined above? Basically, I'm looking to construct a function with type:
interpretDSLWithLog :: Free ClouldFilesF a -> IO ()
I can do this with the REST DSL, but I cannot do it usng logCloudfilesI
.
What is the approach taken when using free monads in these situations? Note
that the problem seems to be the fact that for the logging case, there is no
meaningful value we can supply to the function in ListFiles
to build the
continuation of the program. In a second article
the author uses Halt
, however,
this does not work in my current implementation.