I'm trying to make this an instance of Monad in Haskell:
data Parser a = Done ([a], String) | Fail String
Now I try this code to make it an instance of Monad:
instance Functor Parser where
fmap = liftM
instance Applicative Parser where
pure = return
(<*>) = ap
instance Monad Parser where
return xs = Done ([], xs)
Done (xs, s) >>= f = Done (concat (map f xs)), s)
But this obviously doesn't work, because the function f
in the bind-function is of the type a -> M b
. So the (map f xs)
function yields a list of M b
-things. It should actually make a list of b
's. How can I do this in Haskell?
NOTE: The actual error given by GHC 7.10.3 is:
SuperInterpreter.hs:71:27:
Couldn't match expected type `String' with actual type `a'
`a' is a rigid type variable bound by
the type signature for return :: a -> Parser a
at SuperInterpreter.hs:71:5
Relevant bindings include
xs :: a (bound at SuperInterpreter.hs:71:12)
return :: a -> Parser a (bound at SuperInterpreter.hs:71:5)
In the expression: xs
In the first argument of `Done', namely `([], xs)'
SuperInterpreter.hs:72:45:
Couldn't match type `Parser b' with `[b]'
Expected type: a -> [b]
Actual type: a -> Parser b
Relevant bindings include
f :: a -> Parser b (bound at SuperInterpreter.hs:72:22)
(>>=) :: Parser a -> (a -> Parser b) -> Parser b
(bound at SuperInterpreter.hs:72:5)
In the first argument of `map', namely `f'
In the first argument of `concat', namely `(map f xs)'
Failed, modules loaded: none.
leftaroundabout already showed you some of the problems.
Usually I expect a parser to be some kind of function that takes an input- String
, maybe consuming some of this string and then returning an result together with the unconsumed input.
Based on this idea you can extent your code to do just this:
data ParserResult a
= Done (a, String)
| Fail String
deriving Show
data Parser a = Parser { run :: String -> ParserResult a }
instance Functor Parser where
fmap = liftM
instance Applicative Parser where
pure = return
(<*>) = ap
instance Monad Parser where
return a = Parser $ \ xs -> Done (a, xs)
p >>= f = Parser $ \ xs ->
case run p xs of
Done (a, xs') -> run (f a) xs'
Fail msg -> Fail msg
here is a simple parser that would accept any character:
parseAnyChar :: Parser Char
parseAnyChar = Parser f
where f (c:xs) = Done (c, xs)
f "" = Fail "nothing to parse"
and this is how you would use it:
λ> run parseAnyChar "Hello"
Done ('H',"ello")
λ> run parseAnyChar ""
Fail "nothing to parse"
While it's not completely uncommon to define fmap = liftM
and so on, this is a bit backwards IMO. If you first define the more fundamental instances and base the more involved ones on them, things often come out clearer. I'll leave <*> = ap
, but transform everything else:
instance Functor Parser where -- Note that you can `derive` this automatically
fmap f (Done vs rest) = Done (map f vs) rest
fmap f (Fail err) = Fail err
instance Applicative Parser where
pure xs = Done ([], xs)
(<*>) = ap
Now with fmap
already present, I can define Monad
in the “more mathematical” way: define join
instead of >>=
.
instance Monad Parser where
return = pure
q >>= f = joinParser $ fmap f q
That means you'll work with intuitively handleable concrete values, rather than having to worry about threading a function through a parser. You can therefore see quite clearly what's going on, just write out the recursion:
joinParser :: Parser (Parser a) -> Parser a
joinParser (Fail err) = Fail err
joinParser (Done [] rest) = Done [] rest
joinParser (Done (Fail err : _) _) = Fail err
joinParser (Done (Done v0 rest0 : pss) rest) = ??
at this point you see clearly what Carsten already remarked: your Parser
type doesn't really make sense as a parser. Both the inner and outer Done
wrappers somehow have rest
data; combining it would mean you combine the undone work... this is not what a parser does.
Search the web a bit, there's plenty of material on how to implement parsers in Haskell. In doubt, look how some established library does it, eg parsec .
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.