我一直在探索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
的测试项目中查看该文件