I've been exploring using more newtype
wrappers in my code to make more distinct types. I also do a lot of cheap serialization using Read/Show, particularly as a simple form of strongly-typed config file. I ran into this today:
The example starts like this, and I define a simple newtype to wrap around Int, along with a named field for unwrapping:
module Main where
import Debug.Trace ( trace )
import Text.Read ( readEither )
newtype Bar = Bar { unBar :: Int }
deriving Show
Custom instance to read one of these from a simple Int syntax. The idea here is it would be great to be able to put "42" into a config file instead of "Bar { unBar = 42 }"
This instance also has trace "logging" so we can see when this instance is really used when observing the problem.
instance Read Bar where
readsPrec _ s = [(Bar i, "")]
where i = read (trace ("[debug \"" ++ s ++ "\"]") s)
Now another type containing a Bar. This one will just auto-derive Read.
data Foo = Foo { bar :: Bar }
deriving (Read, Show)
main :: IO ()
main = do
Deserializing the Bar type alone works fine and uses the Read instance above
print $ ((readEither "42") :: Either String Bar)
putStrLn ""
But for some reason Foo, containing a Bar, and automatically derived into Read, is not drilling down and picking up Bar's instances! (Notice that the debug message from trace isn't displayed either)
print $ ((readEither "Foo { bar = 42 }") :: Either String Foo)
putStrLn ""
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)
No! Doesn't work either!! Again, no debug message.
Here's the execution output:
$ stack exec readbug
[debug "42"]
Right (Bar {unBar = 42})
Left "Prelude.read: no parse"
Left "Prelude.read: no parse"
This looks buggy to me but I'd love to hear that I'm doing it wrong.
A fully working example of the code above is available. See the file src/Main.lhs
in a test project on darcshub