Почему этому новому типу не дают правильный экземпляр Read?
Я создал 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
экземпляр для IPAddress
не идентичен тому, что для IP
?
1 ответ
GHC имеет три механизма для получения экземпляров классов типов:
- Нормальный механизм получения, описанный в стандарте Haskell, который может извлекать экземпляры для небольшого предварительно определенного набора классов (
Eq
,Ord
,Enum
,Bounded
,Read
, а такжеShow
).- Набор классов, которые могут быть получены, может быть расширен с помощью
DeriveFunctor
,DeriveFoldable
,DeriveTraversable
, а такжеDeriveLift
расширения, которые при включении обрабатываются так же, как классы, перечисленные в стандарте.
- Набор классов, которые могут быть получены, может быть расширен с помощью
GeneralizedNewtypeDeriving
, который может получить экземпляры, которые относятся к экземплярам в упакованном типе.DeriveAnyClass
, который превращает производные классы в пустые объявления экземпляров.
Здесь есть проблема. Очень легко построить сценарий, в котором для получения экземпляра можно использовать более одного из вышеперечисленных механизмов, и экземпляры могут быть совершенно разными! В вашем примере может применяться как обычное получение, так и получение нового типа. Если вы также включили DeriveAnyClass
Это тоже может быть применимо.
Чтобы устранить неоднозначность, GHC использует жестко закодированные правила, которые вы не можете изменить, которые он пытается сверху вниз:
- Если класс можно получить с помощью обычного механизма получения, используйте его.
- Если
DeriveAnyClass
включен, используйте это. - Если
GeneralizedNewtypeDeriving
включен, и объявленный тип данных является новым типом, используйте его.
Обратите внимание, что это означает включение DeriveAnyClass
а также GeneralizedNewtypeDeriving
в то же время фактически ничего не стоит. Во всяком случае, два нижних правила следует поменять местами, но они не могут быть изменены сейчас.
В вашем случае, так как Read
это класс, для которого экземпляр может быть получен с помощью обычного механизма получения, GHC использует его вместо получения нового типа, и вы получаете поведение, которое видите. Это согласуется с тем, как Show
тоже работает - выводит Show
будет производить экземпляр, который включает в себя IPAddress
на выходе - так Read
должен следовать тому же формату, чтобы удовлетворить один закон Read
есть.
Было бы неплохо, если бы был какой-то механизм, который давал бы указание GHC использовать определенный механизм получения, но в настоящее время его нет. В этом случае вам придется написать экземпляр вручную. К счастью, это не так уж сложно.