在这个答案中,可以看到这样的语句:在实际解析已经完成的情况下,为数据类型Tree
实现Read
实例并不难。
然而,我很难理解像read
这样的函数是如何工作的:AFAIK,我应该实现readsPrec
函数而不是read
,这个readsPrec
应该执行仅由一个字符组成的字符串的读取。这是正确的吗?
如果是这样的话,那么当解析通过ParsecT
完成时,应该如何实现Tree
的Read
实例?我们是否可以逐字分解解析,或者是否有必要这样做?
我不认为read
在Haskell中是一个困难的功能,但现在我发现它非常困惑和混乱,我迷失在搜索Hoogle
中寻找所有这些不熟悉的东西,如readP_to_S
, readS
等。
任何帮助或参考将不胜感激。
我想知道这个问题有一段时间了,你的问题促使我去调查一下。
Summary:最简单的手动方式:
instance Read Tree where
readsPrec _ str = [(parsecRead str,"")]
但deriving
是更安全的选择;上述方法不适用于[Tree]
和其他数据类型。我的理解是Show
和Read
是不打算手动实现;它们应该被派生为,并且在语法正确的Haskell表达式上工作。
看起来Read
不像
class Read a where
read :: String -> a
是存在一个解析器组合子系统,类似于Parsec,但又不同于Parsec,它被设计为模块化、递归等。但是由于我们已经使用了一个不同的解析器组合器库,Parsec,我认为最好尽可能少地干扰其他系统。
Prelude文档说Read
的最小完整实现是readsPrec
或readPrec
。后者被描述为"建议使用新风格的解析器替换readsPrec(仅限GHC)"。这对我来说像是麻烦,所以让我们开始实现readsPrec
。
类型为
readsPrec :: Read a => Int -> ReadS a
type ReadS a = String -> [(a,String)]
和ReadS
的文档读为"a
类型的解析器,表示为一个函数,该函数接受String
并返回一个可能的解析列表作为(a,String)
对"。对我来说,"解析"是什么并不完全明显,但窥视Text.Read
中read
的源代码就会发现:
read :: Read a => String -> a
read s = either errorWithoutStackTrace id (readEither s)
readEither :: Read a => String -> Either String a
readEither s =
-- minPrec is defined as 0 in Text.ParserCombinators.ReadPrec
case [ x | (x,"") <- readPrec_to_S read' minPrec s ] of
[x] -> Right x
[] -> Left "Prelude.read: no parse"
_ -> Left "Prelude.read: ambiguous parse"
where
read' = -- read' :: P.ReadPrec a
do x <- readPrec
lift P.skipSpaces -- P is Text.ParserCombinators.ReadP
return x
我试图扩展readPrec_to_S
等的定义,但我觉得不值得。我认为定义清楚地表明,我们应该返回[(x,"")]
作为成功的解析。
readsPrec
的整型参数似乎是"优先上下文"。我的猜测是,如果我们只想一次解析一个树,忽略它是安全的,但是如果我们稍后尝试解析[Tree]
实例,忽略它将导致问题。我会忽略它,因为我认为不值得这么麻烦。
简而言之,如果我们有你提到的文章中定义的parsecRead :: String -> Tree
(作者称之为read'
)
instance Read Tree where
readsPrec _ str = [(parsecRead str,"")]
如果我们在程序中检查这是如何工作的(使用原始请求者提供的Show
实例):
main = do
print (read "ABC(DE)F" == example)
print ([read "ABC(DE)F", read "ABC(DE)F"] :: [Tree])
print (read "[ABC(DE)F,ABC(DE)F]" :: [Tree])
我们True
[ABC(DE)F,ABC(DE)F]
Test.hs: Prelude.read: no parse
这里的复杂性和缺乏文档实际上使我认为deriving (Read)
实际上是唯一安全的选择,除非您愿意深入研究优先级的细节。我想我在某个地方读到Show
和Read
实际上是主要用于派生,并且字符串旨在语法正确的Haskell表达式(请纠正我,如果我错了)。对于更通用的解析,像Parsec
这样的库可能是更好的选择。
如果你有精力自己去看源代码,相关的模块应该是
-
Text.Read
-
GHC.Read
-
Text.ParserCombinators.ReadP
-
Text.ParserCombinators.ReadPrec