我正在观看Erik Meijer撰写的功能编程基础系列讲座(Graham Hutton的幻灯片)。
在lecture 8 (on functional parsers)中,在定义Parser a
类型后,引入一些解析原语(包括item
和return
,我将其命名为return'
),Erik使用do语法提供simple example如何将解析器组合在一个序列中:
type Parser a = String -> [(a,String)]
item :: Parser Char
item = \inp -> case inp of
[] -> []
(x:xs) -> [(x,xs)]
return' :: a -> Parser a
return' v = \inp -> [(v,inp)]
p :: Parser (Char,Char)
p = do x <- item
item
y <- item
return' (x,y)
然而,当我尝试在GHCi中加载它时,我得到以下类型错误,我没有预料到并且不理解:
Couldn't match type ‘[(Char, String)]’ with ‘Char’
Expected type: String -> [((Char, Char), String)]
Actual type: Parser ([(Char, String)], [(Char, String)])
In a stmt of a 'do' block: return' (x, y)
In the expression:
do { x <- item;
item;
y <- item;
return' (x, y) }
Failed, modules loaded: none.
我做错了什么?
答案 0 :(得分:5)
此错误消息有点令人困惑,因为GHC将您的Parser
类型解压缩到其String -> [(a, String)]
的定义中。如果我们撤消这种转变,它将开始变得更有意义:
Expected type: Parser (Char, Char)
Actual type: Parser ([(Char, String)], [(Char, String)])
现在它开始变得更有意义了。它看起来是你将两次调用的结果带到Parser Char
(即[(Char, String)]
),然后将它们用作正常的Char
s。错误发生在第15行,即您对return'
的调用:
return' (x, y)
这告诉我们x
和y
是两个有问题的结果。
你所拥有的do块正在使用monad实例来处理String -> b
类型的函数;这样做是通过将输入(String -> b
)串起来通过所有函数将它们组合成更大的String
函数。像x <- item
这样的行允许您从b
函数中获取String -> b
元素,同时保持所有管道传递String
参数。不幸的是,item
是Parser Char
,这意味着b
实际上是[(Char, String)]
,而不仅仅是Char
。
需要这个额外返回的结构才能正确解析。 String
表示未用于解析Char
(或您正在解析的任何内容)的其余输入,并且整个事情是一个列表,因为可以有多种可能的方法来有效地解析部分所有需要处理的输入。
要做到这一点,你必须有一些方法来处理这个额外的结构。但是,由于我没有按照课程或观看讲座,我不知道它是什么。您可以定义您自己的 monad实例,而不是依赖于函数的内置实例,但是您可能没有足够深入到自己的类中,或者Meijer可能正在使用非标准库,以避免此问题,在这种情况下,您需要确保您的设置符合类所期望的。
答案 1 :(得分:3)
问题是你的p
定义中的记号没有使用你想要它使用的monad。
我可能错了,但看起来p
正在使用Reader monad版本。
如果您使用此定义,则会看到x
和y
不是Char
值,但类型为[(Char,String)]
:
import Debug.Trace
p :: Parser (Char,Char)
p = do x <- item
y <- item
let foo = trace ("x = " ++ show x) 'a'
bar = trace ("y = " ++ show y) 'b'
return' (foo,bar)
test = p "qwe"
如果您想使用do
表示法,则应为newtype
创建data
或Parser
声明,以便定义绑定运算符(&gt;&gt; =)有效,例如:
newtype Parser a = Parser { parse :: String -> [ (a,String) ] }
instance Monad Parser where
return = Parser . return'
(>>=) = ...
以下是关于如何使用newtype
定义解析器monad实例的好文章:(link)