我从newtype
创建了IP
类别的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
(我想在不创建孤立实例的情况下使其成为ToField
的实例。)
新类型似乎不应该以它应该的方式支持Read
。在此GHCi脚本中,您可以看到给定字符串可以解释为IP
但不能解释为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
无论我是否具有GeneralizedNewtypeDeriving,行为都是相同的。为什么Read
的{{1}}实例与IPAddress
的实例不同?
答案 0 :(得分:7)
GHC有三种推导类型类实例的机制:
Eq
,Ord
,Enum
,{{1} },Bounded
和Read
)。
Show
,DeriveFunctor
,DeriveFoldable
和DeriveTraversable
扩展来扩展可以派生的类集,这些扩展的处理方式与启用时标准中列出的类。GeneralizedNewtypeDeriving
,它可以派生出与包装类型上的实例相关的实例。DeriveAnyClass
,它将派生类转换为空实例声明。这里有一个问题。构建一个场景可以非常容易,其中可以使用多个上述机制来派生实例,并且实例可能完全不同!在您的示例中,普通派生和 newtype派生都可以应用。如果您也启用了DeriveLift
,它也可以适用。
要消除歧义,GHC使用您无法更改的硬编码规则,它从上到下进行尝试:
DeriveAnyClass
,请使用它。DeriveAnyClass
且声明的数据类型是新类型,请使用它。请注意,这意味着同时开启GeneralizedNewtypeDeriving
和DeriveAnyClass
实际上毫无价值。如果有的话,应该交换最后两个规则,但现在不能真正改变它们。
在你的情况下,由于GeneralizedNewtypeDeriving
是一个可以通过普通的派生机制派生实例的类,GHC使用那个而不是使用newtype派生,你得到你看到的行为。这与Read
的工作方式一致,导出Show
也会在输出中生成包含Show
的实例,因此IPAddress
应遵循相同的格式来满足Read
有一项法律。
如果有一些机制来指示GHC使用特定的派生机制会很好,但目前还没有。在这种情况下,您必须手动编写实例。幸运的是,它并不太难。