为什么这个新类型没有被赋予正确的 Read 实例?
Why isn’t this newtype being given the right Read instance?
我从 Data.IP
:
创建了 IP
类型的 newtype
别名
{-# 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,行为都是相同的。为什么 IPAddress
的 Read
实例与 IP
的实例不同?
GHC 具有三种派生类型class 实例的机制:
- Haskell 标准中概述的 normal deriving mechanism,它可以派生一小部分预定义的 class 实例(
Eq
、Ord
、Enum
、Bounded
、Read
和 Show
)。
- 可以使用
DeriveFunctor
、DeriveFoldable
、DeriveTraversable
和 DeriveLift
扩展来扩展可以导出的 classes 集,这启用后,与标准中列出的 classes 的处理方式相同。
GeneralizedNewtypeDeriving
,它可以派生遵循包装类型实例的实例。
DeriveAnyClass
,将派生的 classes 转换为空实例声明。
这里有问题。构造一个场景非常容易,其中可以使用上述机制中的一种以上来派生实例,并且实例可以完全不同!在您的示例中,普通派生 和 新类型派生都可以适用。如果您还启用了 DeriveAnyClass
,它也适用。
为了消除歧义,GHC 使用您无法更改的硬编码规则,它从上到下尝试:
- 如果 class 可以使用普通派生机制派生,请使用它。
- 如果启用了
DeriveAnyClass
,请使用它。
- 如果启用
GeneralizedNewtypeDeriving
并且声明的数据类型是新类型,则使用它。
请注意,这意味着同时打开 DeriveAnyClass
和 GeneralizedNewtypeDeriving
实际上毫无价值。如果有的话,最下面的两条规则应该交换一下,但现在真的不能改变了。
在你的例子中,因为 Read
是一个 class 可以通过普通的派生机制派生出一个实例,GHC 使用那个而不是使用新类型派生,你得到了行为你看。这也与 Show
的工作方式一致——派生 Show
将产生一个在输出中包含 IPAddress
的实例——因此 Read
应该遵循相同的格式以满足一法Read
有。
如果有某种机制可以指示 GHC 使用特定的派生机制就好了,但目前还没有。在这种情况下,您必须手动编写实例。幸运的是,它并不难。
我从 Data.IP
:
IP
类型的 newtype
别名
{-# 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,行为都是相同的。为什么 IPAddress
的 Read
实例与 IP
的实例不同?
GHC 具有三种派生类型class 实例的机制:
- Haskell 标准中概述的 normal deriving mechanism,它可以派生一小部分预定义的 class 实例(
Eq
、Ord
、Enum
、Bounded
、Read
和Show
)。- 可以使用
DeriveFunctor
、DeriveFoldable
、DeriveTraversable
和DeriveLift
扩展来扩展可以导出的 classes 集,这启用后,与标准中列出的 classes 的处理方式相同。
- 可以使用
GeneralizedNewtypeDeriving
,它可以派生遵循包装类型实例的实例。DeriveAnyClass
,将派生的 classes 转换为空实例声明。
这里有问题。构造一个场景非常容易,其中可以使用上述机制中的一种以上来派生实例,并且实例可以完全不同!在您的示例中,普通派生 和 新类型派生都可以适用。如果您还启用了 DeriveAnyClass
,它也适用。
为了消除歧义,GHC 使用您无法更改的硬编码规则,它从上到下尝试:
- 如果 class 可以使用普通派生机制派生,请使用它。
- 如果启用了
DeriveAnyClass
,请使用它。 - 如果启用
GeneralizedNewtypeDeriving
并且声明的数据类型是新类型,则使用它。
请注意,这意味着同时打开 DeriveAnyClass
和 GeneralizedNewtypeDeriving
实际上毫无价值。如果有的话,最下面的两条规则应该交换一下,但现在真的不能改变了。
在你的例子中,因为 Read
是一个 class 可以通过普通的派生机制派生出一个实例,GHC 使用那个而不是使用新类型派生,你得到了行为你看。这也与 Show
的工作方式一致——派生 Show
将产生一个在输出中包含 IPAddress
的实例——因此 Read
应该遵循相同的格式以满足一法Read
有。
如果有某种机制可以指示 GHC 使用特定的派生机制就好了,但目前还没有。在这种情况下,您必须手动编写实例。幸运的是,它并不难。