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)
实例头应该会吓到你。但是我该怎么办呢?:-)