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
Readfor anything more than debugging - and then make sure thatread . show = id. I would put my config in either a JSON (and useaesonto encode/decode), or (if you insist on a custom parser) use something likeattoparsecormegaparsec.Readis a phenomenally inefficient parser, because it is willing to backtrack anywhere. - AlecFoois using theReadinstance forBarthat you wrote! It's just that theFooinstance is failing before it bothers to force the value ofBar(hence never forces the thunk with thetracein it), becauseBarincorrectly reports that it consumed all the remaining input and so theFooreader doesn't see the}it needs to succeed. - Daniel Wagner