将Dict转变为约束

时间:2015-04-07 02:09:03

标签: haskell ghc

我有一个类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 rNum就应该是(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 rFoo 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

虽然这种方法让我得到了我正在寻找的一切,但它似乎很脆弱。我主要担心的是:

  1. 我对模块NumFoo的通用实例头部持谨慎态度。
  2. 如果将任何重叠的实例导入Foo,我突然需要IncoherentInstancesNum上的foo约束将实例选择推迟到运行时。
  3. 是否有另一种方法可以避免在需要Dict的每个函数中使用Num (c m r)来避免这些缺点?

1 个答案:

答案 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 rEqShow的通用实例都完全约束(EntailCyc c, Foo m, Eq/Show/... a),因此对{{1}的约束以上是我需要写的唯一的约束!