Превращение Дикта в ограничение

У меня есть класс Cyc c r, который имеет функции для datas формы c m r, где m - тип phantom. Например,

class Cyc c r where
  cyc :: (Foo m, Foo m') => c m r -> c m' r

У меня есть веские причины не сделать m параметр класса. Для целей этого примера основная причина заключается в том, что он уменьшает количество ограничений на функции. В моем фактическом примере более настоятельная потребность в этом интерфейсе заключается в том, что я работаю с изменяющимися и скрытыми типами phantom, поэтому этот интерфейс позволяет мне получить ограничение Cyc для любого типа phantom.

Единственным недостатком этого выбора является то, что я не могу сделать Num (c m r) ограничение суперкласса Cyc. Мое намерение состоит в том, что c m r должно быть Num при (Cyc c r, Foo m). Текущее решение очень раздражает: я добавил метод к классу Cyc

witNum :: (Foo m) => c m r -> Dict (Num (c m r))

который выполняет одно и то же. Теперь, когда у меня есть функция, которая принимает общий Cyc и нуждается в ограничении Num (c m r), я могу написать:

foo :: (Cyc c r, Foo m) => c m r -> c m r
foo c = case witNum c of
  Dict -> c*2

В курсах я мог бы добавить ограничение Num (c m r) на foo, но я пытаюсь уменьшить количество ограничений, помните? (Cyc c r, Foo m) предполагается подразумевать ограничение Num (c m r) (и мне нужно Cyc c r и Foo m для других целей), поэтому мне также не нужно записывать ограничение Num.

В процессе написания этого вопроса я нашел лучший (?) способ выполнить это, но у него есть свои недостатки.

Модуль Foo:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, ScopedTypeVariables #-}
module Foo where

import Data.Constraint

class Foo m

class Cyc c r where
  cyc :: (Foo m, Foo m') => c m r -> c m' r  
  witNum :: (Foo m) => c m r -> Dict (Num (c m r))

instance (Foo m, Cyc c r) => Num (c m r) where
  a * b = case witNum a of
            Dict -> a * b
  fromInteger a = case witNum (undefined :: c m r) of
                    Dict -> fromInteger a

-- no Num constraint and no Dict, best of both worlds
foo :: (Foo m, Cyc c r) => c m r -> c m r
foo = (*2)

Панель модулей:

{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, OverlappingInstances #-}
module Bar where

import Foo
import Data.Constraint

data Bar m r = Bar r deriving (Show)

instance (Num r) => Cyc Bar r where
  witNum _ = Dict

instance (Num r, Foo m) => Num (Bar m r) where
  (Bar a) * (Bar b) = Bar $ a*b
  fromInteger = Bar . fromInteger

instance Foo ()  

bar :: Bar () Int
bar = foo 3

Хотя этот подход позволяет мне все, что я ищу, кажется хрупким. Мои главные проблемы:

  • В модуле foo я опасаюсь общей главы экземпляра для Num.
  • Если какие-либо совпадающие экземпляры импортируются в foo, мне внезапно требуется IncoherentInstances или ограничение Num на foo, чтобы отложить выбор экземпляра во время выполнения.

Есть ли альтернативный способ избежать использования Dict в каждой функции, которая нуждается в Num (c m r), которая позволяет избежать любого из этих недостатков?

Ответы

Ответ 1

После 6 месяцев мысли я наконец получил ответ на мой болтающийся комментарий выше: добавьте обертку newtype!

Я разделил класс Cyc на два:

class Foo m

class Cyc c where
  cyc :: (Foo m, Foo m') => c m r -> c m' r

class EntailCyc c where
  entailCyc :: Tagged (c m r) ((Foo m, Num r) :- (Num (c m r)))

Затем я определяю свой экземпляр Cyc, как указано выше:

data Bar m r = ...

instance Cyc Bar where ...

instance (Num r, Foo m) => Num (Bar m r) where ...

instance EntailCyc Bar where
  witNum _ = Dict

Затем я определяю оболочку newtype и выдаю для него общий Cyc экземпляр:

newtype W c m r = W (c m r)

instance Cyc (W c m r) where cyc (W a) = W $ cyc a

instance (EntailCyc c, Foo m, Num r) => Num (W c m r) where
  (W a) + (W b) = a + b \\ witness entailCyc a

Наконец, я изменяю все функции, которые использовали общий тип c m r для использования типа W c m r:

foo :: (Cyc c, EntailCyc c, Foo m, Num r) => W c m r -> W c m r
foo = (*2)

Дело здесь в том, что foo может потребоваться множество ограничений (например, Eq (W c m r), Show (W c m r) и т.д.), которые будут индивидуально требовать собственных ограничений. Однако общие экземпляры для W c m r для Eq, Show и т.д. Имеют точно ограничения (EntailCyc c, Foo m, Eq/Show/... a), поэтому ограничения на foo выше являются единственными ограничениями, которые мне нужно написать!