在派生包含数据结构时,不键入类实例

时间:2016-12-16 02:05:03

标签: haskell typeclass deriving

我一直在探索在代码中使用更多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

1 个答案:

答案 0 :(得分:4)

问题出在ReadreadsPrec需要考虑在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时,它会使用BarRead个实例(它不会尝试重新生成Bar将要执行的代码如果您在其上派生了Read,则会生成。