哈斯克尔的名单Monad

时间:2016-03-29 08:40:20

标签: list haskell monads

我试图在Haskell中将其作为Monad的一个实例:

data Parser a = Done ([a], String) | Fail String

现在我尝试使用此代码使其成为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)

但这显然不起作用,因为bind-function中的函数f属于a -> M b类型。因此(map f xs)函数会生成M b - 事物的列表。它实际上应该列出b的列表。我怎么能在Haskell中做到这一点?

注意:GHC 7.10.3给出的实际误差是:

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.

2 个答案:

答案 0 :(得分:4)

左下角已经向您展示了一些问题。

通常我希望解析器是某种带有输入的函数 - String,可能会消耗一些字符串,然后将结果与未消耗的输入一起返回。

基于这个想法,你可以扩展你的代码来做到这一点:

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

一个简单的例子

这是一个可以接受任何字符的简单解析器:

parseAnyChar :: Parser Char
parseAnyChar = Parser f
  where f (c:xs) = Done (c, xs)
        f ""     = Fail "nothing to parse"

这就是你如何使用它:

λ> run parseAnyChar "Hello"
Done ('H',"ello")
λ> run parseAnyChar ""
Fail "nothing to parse"

答案 1 :(得分:3)

虽然定义fmap = liftM等并不常见,但这有点落后于IMO。如果您首先定义更基本的实例并将更多涉及的实例基于它们,那么事情往往会更加清晰。我将离开<*> = ap,但转换其他所有内容:

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

现在fmap已经存在,我可以用“更多数学”的方式定义Monad:定义join而不是>>=

instance Monad Parser where
  return = pure
  q >>= f = joinParser $ fmap f q

这意味着您将使用直观可处理的具体值,而不必担心通过解析器线程化函数。因此,您可以非常清楚地看到发生了什么,只需写出递归:

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) = ??

此时你清楚地看到了Carsten已经注意到的内容:你的Parser类型作为解析器并不真正有意义。内部和外部Done包装器都以某种方式具有rest数据;结合它意味着你将未完成的工作结合起来......这不是解析器的作用。

在网上搜索一下,有很多关于如何在Haskell中实现解析器的资料。有疑问,看看一些已建立的图书馆是如何做到的,e.g. parsec