为什么这个newtype没有被赋予正确的Read实例?

时间:2017-04-03 01:59:19

标签: haskell deriving newtype

我从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的实例不同?

1 个答案:

答案 0 :(得分:7)

GHC有三种推导类型类实例的机制:

  • Haskell标准中概述的normal deriving mechanism,它可以为一组小的预定义类派生实例(EqOrdEnum,{{1} },BoundedRead)。
    • 可以使用ShowDeriveFunctorDeriveFoldableDeriveTraversable扩展来扩展可以派生的类集,这些扩展的处理方式与启用时标准中列出的类。
  • GeneralizedNewtypeDeriving,它可以派生出与包装类型上的实例相关的实例。
  • DeriveAnyClass,它将派生类转换为空实例声明。

这里有一个问题。构建一个场景可以非常容易,其中可以使用多个上述机制来派生实例,并且实例可能完全不同!在您的示例中,普通派生 newtype派生都可以应用。如果您也启用了DeriveLift,它也可以适用。

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

  1. 如果可以使用普通的派生机制派生类,请使用它。
  2. 如果启用了DeriveAnyClass,请使用它。
  3. 如果启用了DeriveAnyClass且声明的数据类型是新类型,请使用它。
  4. 请注意,这意味着同时开启GeneralizedNewtypeDerivingDeriveAnyClass实际上毫无价值。如果有的话,应该交换最后两个规则,但现在不能真正改变它们。

    在你的情况下,由于GeneralizedNewtypeDeriving是一个可以通过普通的派生机制派生实例的类,GHC使用那个而不是使用newtype派生,你得到你看到的行为。这与Read的工作方式一致,导出Show也会在输出中生成包含Show的实例,因此IPAddress应遵循相同的格式来满足Read有一项法律。

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