我有一个类Cyc c r
,其中包含c m r
形式的数据函数,其中m
是幻像类型。例如,
class Cyc c r where
cyc :: (Foo m, Foo m') => c m r -> c m' r
我没有充分的理由不让m
成为类参数。出于此示例的目的,主要原因是它减少了对函数的约束数量。在我的实际例子中,对这个界面更迫切的需求是我使用更改和隐藏的幻像类型,所以这个界面让我得到任何幻像类型的Cyc
约束。
该选择的一个缺点是我无法使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
1}}约束。
在撰写这个问题的过程中,我找到了一种更好的(?)方法来实现这一目标,但它有其自身的缺点。
模块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
虽然这种方法让我得到了我正在寻找的一切,但它似乎很脆弱。我主要担心的是:
Num
中Foo
的通用实例头部持谨慎态度。Foo
,我突然需要IncoherentInstances
或Num
上的foo
约束将实例选择推迟到运行时。是否有另一种方法可以避免在需要Dict
的每个函数中使用Num (c m r)
来避免这些缺点?
答案 0 :(得分: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)
,因此对{{1}的约束以上是我需要写的唯一的约束!