2

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?

bdesham
  • 15,430
  • 13
  • 79
  • 123
  • 1
    Give it a Show instance as well, and see what it prints as. It'll read the same way. – amalloy Apr 03 '17 at 02:00
  • If I understand correctly, the instance generated by `deriving Read` will work exactly the same way as if `IPAddress` were a `data` type – isekaijin Apr 03 '17 at 02:02
  • @amalloy I added `IsString` to the list of derived classes and then I was able to create an `IPAddress` via `"1.2.3.4" :: IPAddress`. Calling `show` on this value gives `"IPAddress 1.2.3.4"`, and indeed `read "IPAddress 1.2.3.4" :: IPAddress` does what I want. I suppose you should turn your comment into an answer! I’d love an explanation of why I have to prepend the value with `IPAddress`, though. – bdesham Apr 03 '17 at 02:10

1 Answers1

8

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:

  1. If the class can be derived using the ordinary deriving mechanism, use that.
  2. If DeriveAnyClass is enabled, use that.
  3. 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.

Alexis King
  • 43,109
  • 15
  • 131
  • 205
  • 1
    "It would be nice if there were some mechanism to instruct GHC to use a particular deriving mechanism". [This is coming in GHC 8.2](https://ghc.haskell.org/trac/ghc/wiki/Commentary/Compiler/DerivingStrategies). – Alec Apr 03 '17 at 06:59
  • @Alec Oh wow, I didn’t know about that! That’s awesome to hear, since it’s a feature I’ve wanted for a while now. – Alexis King Apr 03 '17 at 07:04