I created a newtype
alias of the IP
type from Data.IP
:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module IPAddress (IPAddress) where
import Data.IP (IP)
import Database.PostgreSQL.Simple.ToField
newtype IPAddress = IPAddress IP
deriving (Read, Show)
instance ToField IPAddress where
toField ip = toField $ show ip
(I wanted to make it an instance of ToField
without creating an orphan instance.)
The new type doesn’t seem to support Read
in the way it should, though. In this GHCi transcript, you can see that the given string can be interpreted as an IP
but not as an IPAddress
:
*Main IPAddress> :m + Data.IP
*Main IPAddress Data.IP> read "1.2.3.4" :: IP
1.2.3.4
*Main IPAddress Data.IP> read "1.2.3.4" :: IPAddress
IPAddress *** Exception: Prelude.read: no parse
The behavior is the same regardless of whether I have GeneralizedNewtypeDeriving on. Why is the Read
instance for IPAddress
not identical to the one for IP
?
GHC has three mechanisms for deriving typeclass instances:
- The normal deriving mechanism outlined in the Haskell standard, which can derive instances for a small, predefined set of classes (
Eq
, Ord
, Enum
, Bounded
, Read
, and Show
).
- The set of classes that can be derived can be extended using the
DeriveFunctor
, DeriveFoldable
, DeriveTraversable
, and DeriveLift
extensions, which are treated the same way as the classes listed in the standard when enabled.
GeneralizedNewtypeDeriving
, which can derive instances that defer to instances on the wrapped type.
DeriveAnyClass
, which turns derived classes into empty instance declarations.
There’s a problem here. It is extremely easy to construct a scenario where more than one of the above mechanisms can be used to derive an instance, and the instances can be quite different! In your example, both ordinary deriving and newtype deriving could apply. If you also enabled DeriveAnyClass
, it could apply, too.
To disambiguate, GHC uses hardcoded rules you cannot change, which it tries from top to bottom:
- If the class can be derived using the ordinary deriving mechanism, use that.
- If
DeriveAnyClass
is enabled, use that.
- If
GeneralizedNewtypeDeriving
is enabled and the declared datatype is a newtype, use that.
Note that this means turning on DeriveAnyClass
and GeneralizedNewtypeDeriving
at the same time is effectively worthless. If anything, the bottom two rules should be swapped, but they can’t really be changed now.
In your case, since Read
is a class for which an instance can be derived via the ordinary deriving mechanism, GHC uses that one instead of using newtype deriving, and you get the behavior you see. This is consistent with the way Show
works, too—deriving Show
will produce an instance that includes IPAddress
in the output—so Read
should follow the same format to satisfy the one law Read
has.
It would be nice if there were some mechanism to instruct GHC to use a particular deriving mechanism, but there currently isn’t. In this case, you’ll have to write the instance by hand. Fortunately, it isn’t too hard.