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