Haskell 把口述变成约束

Haskell 把口述变成约束,haskell,ghc,Haskell,Ghc,我有一个类cycr,它具有cmr形式的数据函数,其中m是幻影类型。比如说, class Cyc c r where cyc :: (Foo m, Foo m') => c m r -> c m' r 我有充分的理由不将m作为类参数。在本例中,主要原因是它减少了函数上的约束数量。在我的实际示例中,对该接口更迫切的需求是我处理更改和隐藏的幻影类型,因此该接口允许我获得任何幻影类型的Cyc约束 这种选择的一个缺点是我不能将Num(cmr)作为Cyc的超类约束。我的意图是cmr应该是N

我有一个类
cycr
,它具有
cmr
形式的数据函数,其中
m
是幻影类型。比如说,

class Cyc c r where
  cyc :: (Foo m, Foo m') => c m r -> c m' r
我有充分的理由不将
m
作为类参数。在本例中,主要原因是它减少了函数上的约束数量。在我的实际示例中,对该接口更迫切的需求是我处理更改和隐藏的幻影类型,因此该接口允许我获得任何幻影类型的
Cyc
约束

这种选择的一个缺点是我不能将
Num(cmr)
作为
Cyc
的超类约束。我的意图是
cmr
应该是
Num
,只要
(cycr,foom)
。当前的解决方案非常烦人:我将方法添加到类
Cyc

witNum :: (Foo m) => c m r -> Dict (Num (c m r))
哪种方法能完成同样的事情。现在,当我有一个函数需要一个泛型
Cyc
并需要一个
Num(cmr)
约束时,我可以写:

foo :: (Cyc c r, Foo m) => c m r -> c m r
foo c = case witNum c of
  Dict -> c*2
当然,我可以向
foo
添加
Num(cmr)
约束,但我正在尝试减少约束的数量,记得吗
(cycr,foom)
应该意味着一个
Num(cmr)
约束(我需要
cycr
foom
用于其他目的),所以我也不想写出
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
    中,我会突然需要
    不连贯性
    Foo
    上的
    Num
    约束将实例选择推迟到运行时

  • 有没有其他方法可以避免在每个需要
    Num(cmr)
    的函数中使用
    Dict
    来避免这些缺点呢?

    经过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
    
    然后我定义了一个新类型的包装器,并为它提供了一个通用的
    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
    
    最后,我将所有使用泛型
    cmr
    类型的函数更改为使用
    wcmr
    类型:

    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)
    ,每个约束都需要各自的约束。然而,对于
    Eq
    Show
    等的
    W c m r
    的通用实例都有约束
    (incluationc c,Foo m,Eq/Show/…a)
    ,因此上面关于
    Foo
    的约束是我需要编写的唯一约束

    如果你用惯用的方法来定义witNum,你可以用它来解构,例如,Sub Dict->(*2)(不是更漂亮的)。那
    Num(cmr)
    实例头应该会吓到你。但是我该怎么办呢?:-)