rausted
rausted

Reputation: 959

Haskell foldM from list to a single Either

I'm writing an interpreter of sorts in Haskell, it's been super interesting so far. I'm in the Codegen step (take the parser result, pretty print it into code), One thing I tried to do is the following:

I have Modules and my modules have declarations.

codegen :: Module -> Either String ByteString
codegen Module { what = "module", declarations = decls } = Right $ foldM (\output decl ->
    output ++ (codegenDecl decl)) (empty :: ByteString) decls -- generate declarations    
codegen Module { what = s } = Left $ "Bad module 'what' key " ++ s

codegenDecl :: Declaration -> Either String ByteString
codegenDecl Declaration { what = dt, name = dn, argnames = Just al, constructors = Just lc } = Right $ "Declaration " ++ dn ++ " of type " ++ dt

The pattern-matched variable decls is decls :: [Declaration] and I'm using the Either monad for error tracking. What I expect from

foldM (\output decl -> output ++ (codegenDecl decl)) (empty :: ByteString) decls

Is to concat all the ByteStrings if all the declarations are correct, or return a Left $ "Error writing a declaration"

But I think I'm missing a couple of things here as the typecheker complains. If either of the declarations fails, I want the whole Module to fail. If all of them succeed, I want to concatenate them into one ByteString.

[decls] --------> Right result -------> Right $ foldM (++) accumulator result                                                      |
    |                 ^                                 |
    |                 -----------------------------------
    |
    |-----------> Left err ------------> Left $ err

The bottom part seems to be what the >>= operator does for Either, so that makes me think there's a stylish, monadic way to do what I want without cases and the such. I'd be curious to find out what's the best style here.

Upvotes: 2

Views: 652

Answers (2)

Daniel Wagner
Daniel Wagner

Reputation: 152817

This isn't really an answer to the question, since it doesn't address your questions about foldM... but I wouldn't even use foldM at all. I think it's cleaner to do all your codegenDecling, then concatenate the results separately. This will have two advantages:

  1. It will do a single ByteString concatenation operation, which can build an appropriately-sized buffer first then walk it once to fill it. This will be more efficient than doing repeated appends, which will have to rewalk early ByteStrings many times and allocate many buffers.
  2. Because it uses combinators that can "do less stuff" -- mapM instead of foldM -- the reader can pay less attention and still come to the right conclusion about what's happening.

Here's how that would look:

mconcat <$> mapM codegenDecl decls

Upvotes: 3

Li-yao Xia
Li-yao Xia

Reputation: 33439

  • (++) :: [a] -> [a] -> [a] appends lists

  • output :: ByteString

  • codegenDecl decl :: Either String ByteString

output doesn't match the expected type of the first argument of (++) (unless it was redefined somewhere, e.g., in basic-prelude), and codegenDecl doesn't match the expected type of the second argument of (++).

This lambda should typecheck (with (<>) from Data.Monoid):

\output decl -> fmap (output <>) (codegenDecl decl)

Upvotes: 2

Related Questions