Как установить ограничения на переменную типа вида `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, вызывая странные поломки. Принудительное перенаправление через суперкласс бесполезного ограничения исправляет все.