4

我一直在探索newtype在我的代码中使用更多的包装器来制作更多不同的类型。我还使用 Read/Show 进行了很多廉价的序列化,特别是作为强类型配置文件的简单形式。我今天遇到了这个:

这个例子是这样开始的,我定义了一个简单的 newtype 来包裹 Int,以及一个用于展开的命名字段:

module Main where

import Debug.Trace ( trace )
import Text.Read ( readEither )


newtype Bar = Bar { unBar :: Int }
   deriving Show

自定义实例从简单的 Int 语法中读取其中之一。这里的想法是能够将“42”放入配置文件而不是“Bar { unBar = 42 }”会很棒

该实例还具有跟踪“日志记录”,因此我们可以在观察问题时查看该实例何时真正使用。

instance Read Bar where
   readsPrec _ s = [(Bar i, "")]
      where i = read (trace ("[debug \"" ++ s ++ "\"]") s)

现在是另一种包含 Bar 的类型。这将只是自动派生读取。

data Foo = Foo { bar :: Bar }
   deriving (Read, Show)


main :: IO ()
main = do

单独反序列化 Bar 类型可以正常工作并使用上面的 Read 实例

   print $ ((readEither "42") :: Either String Bar)
   putStrLn ""

但是出于某种原因,包含 Bar 并自动派生到 Read 的 Foo 并没有深入挖掘并拾取 Bar 的实例!(请注意,也不会显示来自跟踪的调试消息)

   print $ ((readEither "Foo { bar = 42 }") :: Either String Foo)
   putStrLn ""

那么好吧,Bar 的默认 Show 表单怎么样,应该与默认的 Read 匹配吗?

   print $ ((readEither "Foo { bar = Bar { unBar = 42 } }") :: Either String Foo)

不!也不行!!同样,没有调试消息。

这是执行输出:

  $ stack exec readbug
  [debug "42"]
  Right (Bar {unBar = 42})

  Left "Prelude.read: no parse"

  Left "Prelude.read: no parse"

这对我来说看起来很麻烦,但我很想听到我做错了。

提供了上述代码的完整工作示例。在 darcshub 上src/Main.lhs的测试项目中查看该文件

4

1 回答 1

4

问题出在Read. readsPrec需要考虑在Bar. 引用前奏

readsPrec d s尝试从字符串的前面解析一个值,返回一个(<parsed value>, <remaining string>)对列表。如果没有成功解析,则返回列表为空。

在你的情况下,你想要:

instance Read Bar where
   readsPrec d s = [ (Bar i, s') | (i, s') <- readsPrec d tracedS ]
      where tracedS = trace ("[debug \"" ++ s ++ "\"]") s

然后,以下工作:

ghci> print $ ((readEither "Foo { bar = 42 }") :: Either String Foo)
[debug " 42 }"]
Right (Foo {bar = Bar {unBar = 42}})

您的另一个问题,即:

那么好吧,Bar 的默认 Show 表单怎么样,应该与默认的 Read 匹配吗?

 print $ ((readEither "Foo { bar = Bar { unBar = 42 } }") :: Either String Foo)

是你的错:你为这样的Read实例定义了一个不是身份操作的实例。当derived 时,它使用s实例(它不会尝试重新生成如果您在其上派生的代码)。Barread . showFooReadBarReadBarRead

于 2016-12-16T02:18:09.457 回答