Это не может быть сделано для работы. Вот проблема:
Incoming Validated (Either [T.Text] Int) ~ Either [T.Text] Int
Incoming ValidationErrors Int ~ Either [T.Text] Int
Теперь, если вы хотите Show (Either [T.Text] Int)
, у вас есть три варианта:
instance (Show a, Show b) => Show (Either a b) -- from Prelude
instance Show baseType => Show (Incoming Validated baseType)
instance Show baseType => Show (Incoming ValidationErrors baseType)
Любой из них будет действующий экземпляр, и GHC требует глобальной уникальности экземпляров. Действительно, проблема в том, что типы семейств не являются инъективными, и поэтому только потому, что вы знаете, что вам нужен instance TyCls A
, GHC не может создать приложение TyFam B1 B2 B3
, которое создаст A
- такое приложение может быть даже не уникальным!
Есть несколько способов исправить это.
Вам действительно нужен экземпляр Show
? Возможно, все, что вам нужно, - это ограничение Show
ограничения функций на функции, которые его хотят использовать. Так, например:
{-# LANGUAGE FlexibleContexts, UndecidableInstances #-}
-- But not FlexibleInstances
deriving instance (Show (Incoming f Int64), Show (Incoming f Tag))
=> Show (NewTag f)
GHC будут распространяться эти ограничения во всем мире, но они всегда выполнимой конечным пользователем. И если f
- это конкретный тип, они полностью исчезнут!
Вы действительно хотите, чтобы Incoming
вещи были неотличимы от базовых типов? Если нет, то вы могли бы использовать GADT здесь:
{-# LANGUAGE GADTs, FlexibleInstances #-}
-- ...
data Incoming :: * -> * -> * where
IncomingValidated :: baseType
-> Incoming Validated baseType
IncomingValidationErrors :: Either [T.Text] baseType
-> Incoming ValidationErrors baseType
-- ...
deriving instance Show (NewTag Validated)
deriving instance Show (NewTag ValidationErrors)
Минус здесь двоякий: во-первых, вы должны шаблон матч везде использовать их; во-вторых, вы не можете (на GHC 7.10, по крайней мере) используют StandaloneDeriving
для экземпляров GADT Show
, вам нужно написать их вручную:
-- deriving instance Show baseType => Show (Incoming Validated baseType)
instance Show baseType => Show (Incoming Validated baseType) where
show (IncomingValidated bt) = "IncomingValidated " ++ show bt
-- deriving instance Show baseType => Show (Incoming ValidationErrors baseType)
instance Show baseType => Show (Incoming ValidationErrors baseType) where
show (IncomingValidationErrors e) = "IncomingValidationErrors " ++ show e
Любой из них может быть хорошим решением; Параметр (1) - это наименьшее изменение от того, что вы уже делаете, и поэтому, вероятно, я буду первым.
Еще одно замечание: в современных (7.10+) GHC мы можем что-то убрать в вашем коде. Прямо сейчас у вас есть два места, которые ваш код допускает слишком большую гибкость.
- Вы можете рассмотреть значение типа
NewTag Bool
или NewTag()
, или ....
- Семейство
Incoming
- это open - любой может добавить type instance Incoming Bool baseType = Maybe baseType
, или Incoming()() = Int
, или ....
Вы только хотите рассмотреть Validated
или ValidationErrors
там, и вы уже написали все возможные случаи семейного типа! GHC предоставляет две функции для улучшения этого: DataKinds
и семейства закрытого типа. С семьями закрытого типа, вы можете написать
type family Incoming validationResult baseType where
Incoming Validated baseType = baseType
Incoming ValidationErrors baseType = Either [T.Text] baseType
Теперь это закрыт - никто никогда не может добавить новый случай. Это решает # 2.
Что касается №1, если мы включим DataKinds
, GHC автоматически продвигает наши конструкторы значений до уровня уровня! Так как у нас есть Int :: *
, у нас есть 'False :: Bool
- '
указывает GHC, что мы находимся на уровне уровня. Добавление этой функции выглядит следующим образом:
{-# LANGUAGE DataKinds #-}
-- ...
data ValidationResult = Validated | ValidationErrors
deriving (Eq, Ord, Enum, Bounded, Show, Read)
---- EITHER:
---- Option (1), with a type family
-- The only change here is to add tick marks!
type family Incoming validationResult baseType where
Incoming 'Validated baseType = baseType
Incoming 'ValidationErrors baseType = Either [T.Text] baseType
---- OR:
---- Option (2), with a GADT
-- Here, we change the kind signature and add tick marks
data Incoming :: ValidationResult -> * -> * where
IncomingValidated :: baseType
-> Incoming 'Validated baseType
IncomingValidationErrors :: Either [T.Text] baseType
-> Incoming 'ValidationErrors baseType
Мы можем также добавить добрые подписи, если мы хотим, - type family Incoming (validationResult :: ValidationResult) (baseType :: *) :: * where …
или data NewTag (f :: ValidationResult) = …
, но те будут судить, и, следовательно, являются необязательными.
Если клещ действительно вас раздражает, вы можете использовать следующий трюк, который я взял из исходного кода GHC:
type Validated = 'Validated
type ValidationErrors = 'ValidationErrors
ОК, еще один тип уровня забавная вещь, потому что я не могу сопротивляться :-) Давайте рассмотрим вариант (1) снова, с типом семейства. Мы должны обеспечить это раздражающее ограничение (Show (Incoming f Int64), Show (Incoming f Tag))
везде, которое является довольно громоздким, особенно если мы хотим абстрагироваться над ним - для создания экземпляра Eq
, это то же самое, но с Eq
вместо Show
. А что, если есть больше полей?
Если мы включили ConstraintKinds
, мы сможем связаться с нами can аннотация с ограничениями. Это работает так:
{-# LANGUAGE ConstraintKinds #-}
import GHC.Exts (Constraint)
type NewTagFieldsAre (c :: * -> Constraint) f =
(c (Incoming f Int64), c (Incoming f Tag))
(Нам нужна такая подпись так GHC не думаю, что это производит обычный кортеж.) Затем мы можем указать
deriving instance NewTagFieldsAre Eq f => Eq (NewTag f)
deriving instance NewTagFieldsAre Ord f => Ord (NewTag f)
deriving instance NewTagFieldsAre Show f => Show (NewTag f)
deriving instance NewTagFieldsAre Read f => Read (NewTag f)
И все намного короче!
Составляя это все вместе, вот какой вариант (1) выглядит с типом семейства. Единственное, что отличается от этого, это то, что я укрепил изменения, которые я сделал, немного переформатировал и сделал несколько других изменений на основе вкуса.
{-# LANGUAGE FlexibleContexts, UndecidableInstances, TypeFamilies,
ConstraintKinds, DataKinds, StandaloneDeriving #-}
import Data.Text as T
import Data.Int (Int64)
import GHC.Exts (Constraint)
data Tag = Tag { unTag :: T.Text } deriving (Eq, Ord, Show, Read)
data ValidationResult = Validated | ValidationErrors
deriving (Eq, Ord, Enum, Bounded, Show, Read)
type family Incoming (vres :: ValidationResult) (base :: *) :: * where
Incoming 'Validated base = base
Incoming 'ValidationErrors base = Either [T.Text] base
data NewTag f = NewTag { ntClientId :: Incoming f Int64
, ntTag :: Incoming f Tag }
type NewTagFieldsAre (c :: * -> Constraint) f =
(c (Incoming f Int64), c (Incoming f Tag))
deriving instance NewTagFieldsAre Eq f => Eq (NewTag f)
deriving instance NewTagFieldsAre Ord f => Ord (NewTag f)
deriving instance NewTagFieldsAre Show f => Show (NewTag f)
deriving instance NewTagFieldsAre Read f => Read (NewTag f)
И для полноты картины, опция GADT:
{-# LANGUAGE GADTs, FlexibleInstances, TypeFamilies, DataKinds,
StandaloneDeriving #-}
import Data.Text as T
import Data.Int (Int64)
data Tag = Tag { unTag :: T.Text } deriving (Eq, Ord, Show, Read)
data ValidationResult = Validated | ValidationErrors
deriving (Eq, Ord, Enum, Bounded, Show, Read)
data Incoming :: ValidationResult -> * -> * where
IncomingValidated :: base
-> Incoming Validated base
IncomingValidationErrors :: Either [T.Text] base
-> Incoming ValidationErrors base
instance Eq base => Eq (Incoming Validated base) where
IncomingValidated x == IncomingValidated y = x == y
instance Eq base => Eq (Incoming ValidationErrors base) where
IncomingValidationErrors ex == IncomingValidationErrors ey = ex == ey
instance Ord base => Ord (Incoming Validated base) where
IncomingValidated x `compare` IncomingValidated y = x `compare` y
instance Ord base => Ord (Incoming ValidationErrors base) where
IncomingValidationErrors ex `compare` IncomingValidationErrors ey = ex `compare` ey
instance Show base => Show (Incoming Validated base) where
show (IncomingValidated x) = "IncomingValidated " ++ show x
instance Show base => Show (Incoming ValidationErrors base) where
show (IncomingValidationErrors ex) = "IncomingValidationErrors " ++ show ex
-- `Show` properly handling precedence, along with the `Read` instance, are left
-- as an exercise for the interested reader.
data NewTag f = NewTag { ntClientId :: Incoming f Int64
, ntTag :: Incoming f Tag }
deriving instance Eq (NewTag Validated)
deriving instance Eq (NewTag ValidationErrors)
deriving instance Ord (NewTag Validated)
deriving instance Ord (NewTag ValidationErrors)
deriving instance Show (NewTag Validated)
deriving instance Show (NewTag ValidationErrors)
Это необходимо вручную вывести экземпляры действительно перетащить его вниз!
Пожалуйста, убедитесь, что ваш пример завершен; это не компилируется как есть (отсутствует модуль импорта и определение 'Tag'). Кроме того, включайте сообщения об ошибках, которые вы получаете; они часто предлагают предложение (здесь это «Использовать FlexibleInstances, если вы хотите отключить это»). Сообщите нам, что вы сделали - вы попробовали предложение? С чем вы столкнулись? –
Подвешивание ... фиксирование всего этого. –
@ AntalSpector-Zabusky отредактировал вопрос. Это приемлемо сейчас? –