派生包含数据结构时未使用类型 class 实例

Type class instance not being used when deriving containing data structure

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

示例是这样开始的,我定义了一个简单的新类型来包装 Int,以及一个用于展开的命名字段:

module Main where

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


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

自定义实例以通过简单的 Int 语法读取其中之一。这里的想法是能够将“42”而不是 "Bar { unBar = 42 }"

放入配置文件中会很棒

这个实例也有跟踪"logging"所以我们在观察问题的时候可以看到这个实例什么时候真正被使用了。

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 form 怎么样,应该与默认的 Read right 匹配吗?

   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"

这对我来说看起来有问题,但我很乐意听到我做错了。

上面代码的完整示例可用。在 test project on darcshub

中查看文件 src/Main.lhs

问题出在ReadreadsPrec 需要考虑在 Bar 之后可能会看到更多内容的可能性。 Quoting the Prelude:

readsPrec d s attempts to parse a value from the front of the string, returning a list of (<parsed value>, <remaining string>) pairs. If there is no successful parse, the returned list is empty.

在你的情况下,你想要:

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}})

你的另一个问题,即:

So ok, how about the default Show form for Bar, should match the default Read right?

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

是你的错:你为 Bar 定义了一个 Read 实例,因此 read . show 不是身份操作。当 Foo 派生 Read 时,它使用 Bars Read 实例(它不会尝试重新生成 Bar 如果您有派生Read就可以了)。