将字典变成约束

Turning a Dict into a constraint

我有一个 class Cyc c r,它具有 c m r 形式的数据函数,其中 m 是幻像类型。例如,

class Cyc c r where
  cyc :: (Foo m, Foo m') => c m r -> c m' r

我有充分的理由不将 m 设为 class 参数。出于本示例的目的,主要原因是它减少了对函数的约束数量。在我的实际示例中,对这个接口的一个更迫切的需求是我使用更改和隐藏的幻像类型,所以这个接口让我可以为任何幻像类型获得 Cyc 约束。

该选择的一个缺点是我无法使 Num (c m r) 成为 Cyc 的超级class 约束。我的意图是,只要 (Cyc c r, Foo m)c m r 就应该是 Num。当前的解决方案非常烦人:我将方法添加到 class 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约束也。

在写这个问题的过程中,我发现了一个更好的(?)方法来完成这个,但是它有它自己的缺点。

模块 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. 我对模块 FooNum 的通用实例头持谨慎态度。
  2. 如果将任何重叠实例导入 Foo,我突然需要 IncoherentInstancesfoo 上的 Num 约束以将实例选择推迟到运行时。

是否有其他方法可以避免在每个需要 Num (c m r) 的函数中使用 Dict,从而避免这些缺点?

经过 6 个月的思考,我终于对上面悬而未决的评论有了答案:添加一个 newtype 包装器!

我将 Cyc class 一分为二:

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

然后我定义一个新类型包装器并为其提供一个通用 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) 等)需要自己的约束。但是,EqShow 等的 W c m r 的通用实例都有 完全 约束 (EntailCyc c, Foo m, Eq/Show/... a),因此约束foo以上是我需要写的只有个约束!