我一直在探索在代码中使用更多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的类型。这个只会自动导出Read。
data Foo = Foo { bar :: Bar }
deriving (Read, Show)
main :: IO ()
main = do
单独反序列化Bar类型可以正常工作并使用上面的Read实例
print $ ((readEither "42") :: Either String Bar)
putStrLn ""
但是出于某种原因,Foo,包含一个Bar,并自动导入到Read中,并没有向下钻取并捡起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
答案 0 :(得分:4)
问题出在Read
。 readsPrec
需要考虑在Bar
之后可能会看到更多内容的可能性。 Quoting the Prelude:
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 form如何与默认的Read right匹配?
print $ ((readEither "Foo { bar = Bar { unBar = 42 } }") :: Either String Foo)
是您的错:您为Read
定义了Bar
个实例,read . show
不是身份操作。当Foo
派生Read
时,它会使用Bar
个Read
个实例(它不会尝试重新生成Bar
将要执行的代码如果您在其上派生了Read
,则会生成。