【发布时间】:2016-12-16 02:05:02
【问题描述】:
我一直在探索在我的代码中使用更多 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 }”会很棒
这个实例也有trace "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 表单怎么样,应该和默认的 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"
这对我来说看起来有问题,但我很想听到我做错了。
提供了上述代码的完整工作示例。在test project on darcshub 中查看文件src/Main.lhs
【问题讨论】:
-
这是一个非常好的问题。我喜欢你让某人开始调试你的代码变得多么容易。希望我的回答有助于确定您遇到的特定问题。除此之外,我不建议将
Read用于除调试以外的任何事情 - 然后确保使用read . show = id。我会将我的配置放在 JSON 中(并使用aeson进行编码/解码),或者(如果您坚持使用自定义解析器)使用类似attoparsec或megaparsec的东西。Read是一个非常低效的解析器,因为它愿意在任何地方回溯。 -
你错了:
Foo的派生实例是使用你写的Bar的Read实例!只是Foo实例在它费力强制Bar的值之前失败(因此从不强制其中包含trace的thunk),因为Bar错误地报告它消耗了所有剩余的输入并且所以Foo读者看不到}它需要成功。 -
@Alec 我没有考虑过使用 JSON 进行配置。保持打字和层次结构。然后你最终得到一个可供其他语言/系统使用的配置文件。我现在用新类型来探索一下。谢谢!
标签: haskell typeclass deriving