finally tagless parsing and recursive monadic actions

530 Views Asked by At

i deserialize a data structure from disc in the finally tagless style. i.e.

class SYM repl where
    a :: repl
    include :: FilePath -> repl 

myParser :: SYM r => Parser r

the language i am parsing has include directives.

i am using attoparsec which is not a monad transformer, so i cannot simply supply a type Loader = FilePath -> IO (Maybe Text).

i can write an interpreter with the following SYM instance, that resolves an include.

instance SYM r => SYM (Loader -> IO (Either String r)) where
    include path loader = 
        maybe (Left "cannot load") (parseOnly myParser) <$> loader path

unfortunately includes in the included file don't get resolved. of course i can resolve them twice to resolve the next layer. but that leads to infinite types if i want to do that for every possible level.

right now i preload all the includes (in a HashMap) and bind them, so i can pass a FilePath -> Maybe Text to the parser and resolve includes there, but that is clearly not optimal. (and include is not part of SYM anymore.)

my question is, how does a finally tagless style deal with that problem?

edit: i published a complete example on lpaste: http://lpaste.net/105182

1

There are 1 best solutions below

0
On BEST ANSWER

It was pretty easy in hindsight, but it usually is.

The one-level-only resolving is simply the following.

instance SYM r => SYM (Loader -> IO (Either String r)) where
    token t _ = return . Right $ token t
    include path loader =
        maybe (Left "cannot load") (parseOnly myParser) <$> loader path

I.e. it will run parseOnly myParser :: Either String r on the (successfully) loaded file.

The resolve-everything will just need to select the SYM (Loader -> IO (Either String r)) instance for the myParser and add the loader argument:

    include path loader =
        maybe (return $ Left "cannot load")
              (either (return ∘ Left) ($ loader) . parseOnly myParser)
              =<< loader path

The crucial step is that it will supply the additional parameter loader to the newly parseOnlyd SYM repl, thus selecting the right instance.

A complete snippet is in the annotated lambda paste: http://lpaste.net/105182. test it with entering "include include token"