Превращение Дикта в ограничение
У меня есть класс 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
выше являются единственными ограничениями, которые мне нужно написать!