Why isn’t this newtype being given the right Read instance?

不打扰是莪最后的温柔 提交于 2019-12-08 01:04:10

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/43175498/why-isn-t-this-newtype-being-given-the-right-read-instance

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!