2

我从以下类型创建了一个newtype别名:IPData.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 成绩单中,您可以看到给定的字符串可以解释为 anIP但不能解释为 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

无论我是否启用了 GeneralizedNewtypeDeriving,行为都是相同的。为什么 for 的Read实例IPAddress与 for 的实例不同IP

4

1 回答 1

8

GHC 具有三种派生类型类实例的机制:

  • Haskell 标准中概述的正常派生机制,它可以为一小部分预定义的类(EqOrdEnumBoundedReadShow)派生实例。
    • DeriveFunctor可以使用、DeriveFoldableDeriveTraversable和扩展来扩展可以派生的类集,这些DeriveLift扩展在启用时与标准中列出的类的处理方式相同。
  • GeneralizedNewtypeDeriving,它可以派生遵循包装类型上的实例的实例。
  • DeriveAnyClass,它将派生类变成空的实例声明。

这里有问题。构建一个场景是非常容易的,其中一个以上的机制可以用于派生一个实例,并且实例可以完全不同!在您的示例中,普通派生新类型派生都可以应用。如果您还启用DeriveAnyClass了 ,它也可以应用。

为了消除歧义,GHC 使用您无法更改的硬编码规则,它会从上到下尝试:

  1. 如果可以使用普通派生机制派生类,请使用它。
  2. 如果DeriveAnyClass启用,请使用它。
  3. 如果GeneralizedNewtypeDeriving启用并且声明的数据类型是新类型,请使用它。

请注意,这意味着打开,DeriveAnyClass同时GeneralizedNewtypeDeriving实际上是毫无价值的。如果有的话,应该交换底部的两条规则,但现在不能真正改变它们。

在您的情况下,由于Read是可以通过普通派生机制为其派生实例的类,因此 GHC 使用该类而不是使用 newtype 派生,并且您会得到所看到的行为。这也与Show工作方式一致——推导Show将产生一个包含IPAddress在输出中的实例——因此Read应该遵循相同的格式以满足一个定律Read

如果有某种机制来指示 GHC 使用特定的派生机制,那就太好了,但目前还没有。在这种情况下,您必须手动编写实例。幸运的是,这并不难。

于 2017-04-03T02:15:27.590 回答