У меня есть класс 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), которая позволяет избежать любого из этих недостатков?