Как установить ограничения на переменную типа вида `Constraint`?
Я играю с расширением ConstraintKinds
GHC.
У меня есть следующий тип данных, который представляет собой всего лишь поле для вещей, выполняющих ограничение одного параметра c
:
data Some (c :: * -> Constraint) where
Some :: forall a. c a => a -> Some c
Например, я мог бы построить поле с некоторым номером (возможно, не очень полезным).
x :: Some Num
x = Some (1 :: Int)
Теперь, пока c
содержит ограничение Show
, я мог бы предоставить экземпляр Show (Some c)
.
instance ??? => Show (Some c) where
show (Some x) = show x -- Show dictionary for type of x should be in scope here
Но как я могу выразить это требование в контексте экземпляра (помечено ???
)?
Я не могу использовать ограничение равенства (c ~ Show
), потому что они не обязательно равны. c
может быть Num
, что означает, но не равно, Show
.
Edit
Я понял, что это вообще невозможно.
Если у вас есть два значения типа Some Eq
, их невозможно сопоставить для равенства. Они могут быть разных типов, каждый из которых имеет собственное понятие равенства.
То, что применяется к Eq
, применяется к любому типу класса, в котором параметр типа отображается в правой части первой стрелки функции (например, второй a
в (==) :: a -> a -> Bool
).
Учитывая, что нет способа создать ограничение, выражающее "эта переменная типа не используется за первой стрелкой", я не думаю, что можно написать экземпляр, который я хочу написать.
Ответы
Ответ 1
Ближайшим, который мы можем получить, является класс Class1
, который reifys отношения между классом и единственным суперклассическим ограничением как класс. Он основан на Class
от constraints.
Сначала мы кратко рассмотрим пакет ограничений. A Dict
фиксирует словарь для Constraint
data Dict :: Constraint -> * where
Dict :: a => Dict a
:-
фиксирует, что одно ограничение влечет за собой другое. Если мы имеем a :- b
, всякий раз, когда у нас есть ограничение a
, мы можем создать словарь для ограничения b
.
newtype a :- b = Sub (a => Dict b)
Нам нужно доказательство, подобное :-
, нам нужно знать, что forall a. h a :- b a
или h a => Dict (b a)
.
Одиночное наследование
Фактически реализация этого для Class
es только с одним наследованием требует кухонной раковины языковых расширений, включая OverlappingInstances
.
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverlappingInstances #-}
import Data.Constraint
Мы определим класс ограничений вида k -> Constraint
, где ограничение имеет один суперкласс.
class Class1 b h | h -> b where
cls1 :: h a :- b a
Теперь мы готовы решить нашу примерную проблему. У нас есть класс a
, для которого требуется экземпляр Show
.
class Show a => A a
instance A Int
Show
является суперклассом a
instance Class1 Show A where
cls1 = Sub Dict
Мы хотим написать экземпляры Show
для Some
data Some (c :: * -> Constraint) where
Some :: c a => a -> Some c
Мы можем Show
a Some Show
.
instance Show (Some Show) where
showsPrec x (Some a) = showsPrec x a
Мы можем Show
a Some h
, когда h
имеет один суперкласс b
, и мы могли бы показать Some b
.
instance (Show (Some b), Class1 b h) => Show (Some h) where
showsPrec x (Some (a :: a)) =
case cls1 :: h a :- b a of
Sub Dict -> showsPrec x ((Some a) :: Some b)
Это позволяет нам писать
x :: Some A
x = Some (1 :: Int)
main = print x
Ответ 2
Вы не можете сделать Some c
экземпляр Show
, кроме тривиально.
Вы хотите Show
a
внутри Some
, но эта переменная экзистенциально квантуется, поэтому мы не можем зависеть от какого-либо знания типа a
. В частности, мы не знаем, что a
является экземпляром Show
.
EDIT: Я расскажу о своем ответе.
Даже с большим количеством машин и отказом от экземпляра Show
, я все еще не думаю, что то, что вы хотите, возможно из-за экзистенциальной квантификации.
Сначала я переписал Some
в более знакомой форме
data Dict p where
Dict :: p a => a -> Dict p
Обычный способ говорить о "ограничениях, подразумевающих ограничения", - это концепция вложения ограничений.
data p :- q where
Sub :: p a => Dict q -> p :- q
Мы можем думать о значении типа p :- q
как доказательстве того, что если выполняется ограничение forall a. p a
, то forall a. q a
следует.
Теперь мы попытаемся написать разумную функцию Show
-ish
showD :: p :- Show -> Dict p -> String
showD (Sub (Dict a)) (Dict b) = show b
С первого взгляда это может сработать. Мы применили следующие ограничения в области видимости (простить синтаксис pseudo- exists
)
(0) p :: * -> Constraint
(1) exists a. p a -- (Dict p)
(2) exists b. p b => Show b -- (p :- Show)
Но теперь все разваливается, GHC справедливо жалуется:
main.hs:10:33:
Could not deduce (Show a2) arising from a use of `show'
from the context (p a)
bound by a pattern with constructor
Sub :: forall (p :: * -> Constraint) (q :: * -> Constraint) a.
(p a) =>
Dict q -> p :- q,
in an equation for `showD'
at main.hs:10:8-19
or from (Show a1)
bound by a pattern with constructor
Dict :: forall (p :: * -> Constraint) a. (p a) => a -> Dict p,
in an equation for `showD'
at main.hs:10:13-18
or from (p a2)
bound by a pattern with constructor
Dict :: forall (p :: * -> Constraint) a. (p a) => a -> Dict p,
in an equation for `showD'
at main.hs:10:23-28
поскольку невозможно объединить a
из (1)
с b
из (2)
.
Это та же самая существенная идея, которая используется в пакете constraints
, упомянутом в комментариях.
Ответ 3
Новое расширение QuantifiedConstraints
позволяет это.
class (a => b) => Implies a b where
instance (a => b) => Implies a b where
instance (forall a. c a 'Implies' Show a) => Show (Some c) where
show (Some x) = show x
Внутри тела экземпляра Show
, как будто есть
instance forall a. Implies (c a) (Show a)
в рамках. Если у вас есть T :: Type
и вы знаете c T
, то суперкласс c T => Show T
специализированного экземпляра Implies (c T) (Show T)
позволяет вам получить Show T
Необходимо использовать Implies
вместо прямой формы forall a. ca => Show a
forall a. ca => Show a
ограничение. Это неверное ограничение действует как
instance forall a. c a => Show a
который перекрывается с каждым экземпляром Show
, вызывая странные поломки. Принудительное перенаправление через суперкласс бесполезного ограничения исправляет все.