如何在实际解析已经完成的情况下,在Haskell中实现Read实例



在这个答案中,可以看到这样的语句:在实际解析已经完成的情况下,为数据类型Tree实现Read实例并不难。

然而,我很难理解像read这样的函数是如何工作的:AFAIK,我应该实现readsPrec函数而不是read,这个readsPrec应该执行仅由一个字符组成的字符串的读取。这是正确的吗?
如果是这样的话,那么当解析通过ParsecT完成时,应该如何实现TreeRead实例?我们是否可以逐字分解解析,或者是否有必要这样做?

我不认为read在Haskell中是一个困难的功能,但现在我发现它非常困惑和混乱,我迷失在搜索Hoogle中寻找所有这些不熟悉的东西,如readP_to_S, readS等。

任何帮助或参考将不胜感激。

我想知道这个问题有一段时间了,你的问题促使我去调查一下。

Summary:最简单的手动方式:

instance Read Tree where
    readsPrec _ str = [(parsecRead str,"")]

deriving是更安全的选择;上述方法不适用于[Tree]和其他数据类型。我的理解是ShowRead不打算手动实现;它们应该被派生为,并且在语法正确的Haskell表达式上工作。


看起来Read不像

那么简单的原因
class Read a where
    read :: String -> a

是存在一个解析器组合子系统,类似于Parsec,但又不同于Parsec,它被设计为模块化、递归等。但是由于我们已经使用了一个不同的解析器组合器库,Parsec,我认为最好尽可能少地干扰其他系统。

Prelude文档说Read的最小完整实现是readsPrecreadPrec。后者被描述为"建议使用新风格的解析器替换readsPrec(仅限GHC)"。这对我来说像是麻烦,所以让我们开始实现readsPrec

类型为

readsPrec :: Read a => Int -> ReadS a
type ReadS a = String -> [(a,String)]

ReadS的文档读为"a类型的解析器,表示为一个函数,该函数接受String并返回一个可能的解析列表作为(a,String)对"。对我来说,"解析"是什么并不完全明显,但窥视Text.Readread的源代码就会发现:

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)实际上是唯一安全的选择,除非您愿意深入研究优先级的细节。我想我在某个地方读到ShowRead实际上是主要用于派生,并且字符串旨在语法正确的Haskell表达式(请纠正我,如果我错了)。对于更通用的解析,像Parsec这样的库可能是更好的选择。

如果你有精力自己去看源代码,相关的模块应该是

  • Text.Read
  • GHC.Read
  • Text.ParserCombinators.ReadP
  • Text.ParserCombinators.ReadPrec

相关内容

最新更新