将字典变成约束 [英] Turning a Dict into a constraint
问题描述
我有一个类 Cyc cr
,它具有形式为 cmr
的数据的函数,其中 m
是幻影类型。例如,
class Cyc c r其中
cyc ::(Foo m,Foo m')=> c m r - > c m'r
我有充分的理由不让 m
一个类参数。就本例而言,主要原因是它减少了函数约束的数量。在我的实际例子中,这个接口更引人注目的需求是我使用变化和隐藏的幻像类型,所以这个接口让我得到任何幻像类型的
Cyc
约束。
这个选择的一个缺点是我不能让 Num(cmr)
超类约束的Cyc
。我的意图是,只要(Cyc cr,Foo m)
。目前的解决方案非常烦人:我添加了方法到类 cmr
应该是 Num
Cyc
witNum ::(Foo m)=> c m r - > Dict(Num(c m r))
哪种排序完成同样的事情。现在,当我有一个函数需要一个通用的 Cyc
并且需要一个 Num(cmr)
约束时,我可以这样写:
foo ::(Cyc cr,Foo m)=> c m r - > c m r
foo c = case $ c
Dict - > c * 2
课程我可以添加 Num(cmr)
约束为 foo
,但我试图减少约束的数量,请记住? (Cyc cr,Foo m)
应该暗示 Num(cmr)
约束(并且我需要 Cyc cr
和 Foo m
用于其他目的),所以我不想写出 Num
约束也是。
在写这个问题的过程中,我发现了一个更好的方法来完成这个任务,它自己的缺点。
模块Foo:
{ - #LANGUAGE MultiParamTypeClasses,FlexibleInstances,ScopedTypeVariables# - }
module Foo其中
import Data.Constraint
class Foo m
class Cyc cr 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)其中
a * b =案例a
Dict - > a * b
fromInteger a = case witNum(undefined :: c m r)of
Dict - > fromInteger a
- 不是Num约束并且不是Dict,两者都是最好的
foo ::(Foo m,Cyc c r)=> c m r - > cmr
foo =(* 2)
模块栏:
{ - #LANGUAGE FlexibleInstances,MultiParamTypeClasses,OverlappingInstances# - }
模块Bar其中
导入Foo
import Data.Constraint
data Bar mr = Bar r derived(Show)
instance(Num r)=> Cyc Bar r其中
witNum _ =字典
实例(Num r,Foo m)=> Num(Bar m r)其中
(Bar a)*(Bar b)= Bar $ a * b
fromInteger = Bar。 fromInteger
instance Foo()
bar :: Bar()Int
bar = foo 3
尽管这种方法让我找到了我所需要的一切,但它似乎很脆弱。我主要关注的是:
- 我对
Num
模块Foo
。 - 如果有任何重叠的实例被导入到
Foo
,我突然需要IncoherentInstances
或foo
上的Num
将实例选择推迟到运行时。
有没有其他方法可以避免使用 Dict
在每个需要 Num(cmr)
的函数中,避免这些缺点之一?
newtype
包装器! 我将 Cyc
类分成两部分:
class Foo m
class Cyc c其中
cyc ::(Foo m,Foo m')=> c m r - > c m'r
class EntailCyc c其中
entailCyc :: Tagged(cmr)((Foo m,Num r): - (Num(cmr)))
然后我像上面那样定义我的 Cyc
实例。
数据Bar mr = ...
实例Cyc Bar其中...
instance (Num r,Foo m)=> Num(Bar mr)where ...
instance EntailCyc Bar其中
witNum _ =字典
然后我定义一个新类型的包装器并为它提供一个通用的 Cyc
实例:
<$ (w cmr)其中cyc(W a)= W $ cyc a
$ b $($)$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ b实例(EntailCyc c,Foo m,Num r)=> Num(W cmr)其中
(W a)+(W b)= a + b \\\证人entailCyc a
最后,我改变了所有使用通用 cmr
类型的函数来使用 W cmr
type:
foo ::(Cyc c,EntailCyc c,Foo m,Num r)=> W c m r - > W cmr
foo =(* 2)
这里的要点是 foo
可能需要许多约束(例如 Show (W cmr)
等),每个单独需要自己的约束。但是,对于 Eq
,显示
的
I have a class Cyc c r
which has functions for datas of the form c m r
, where m
is a phantom type. For example,
class Cyc c r where
cyc :: (Foo m, Foo m') => c m r -> c m' r
I do have good reasons for not making m
a class parameter. For the purposes of this example, the primary reason is that it reduces the number of constraints on functions. In my actual example, a more compelling need for this interface is that I work with changing and hidden phantom types, so this interface lets me get a Cyc
constraint for any phantom type.
One downside to that choice is that I can't make Num (c m r)
a superclass constraint of Cyc
. My intention is that c m r
should be a Num
whenever (Cyc c r, Foo m)
. The current solution is very annoying: I added method to class Cyc
witNum :: (Foo m) => c m r -> Dict (Num (c m r))
which sort-of accomplishes the same thing. Now when I have a function that takes a generic Cyc
and needs a Num (c m r)
constraint, I can write:
foo :: (Cyc c r, Foo m) => c m r -> c m r
foo c = case witNum c of
Dict -> c*2
Of courses I could add a Num (c m r)
constraint to foo
, but I'm trying to reduce the number of constraints, remember? (Cyc c r, Foo m)
is supposed to imply a Num (c m r)
constraint (and I need Cyc c r
and Foo m
for other purposes), so I don't want to have to write out the Num
constraint also.
In the process of writing this question, I found a better(?) way to accomplish this, but it has its own drawbacks.
Module 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)
Module Bar:
{-# 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
While this approach gets me everything I'm looking for, it seems fragile. My main concerns are:
- I'm wary of the generic instance head for
Num
in moduleFoo
. - If any overlapping instances are imported into
Foo
, I suddenly needIncoherentInstances
or theNum
constraint onfoo
to defer instance selection to runtime.
Is there an alternative way to avoid using Dict
in every function that needs Num (c m r)
that avoids either of these downsides?
After 6 months of thought, I finally have an answer to my dangling comment above: add a newtype
wrapper!
I split the Cyc
class in two:
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)))
Then I define my Cyc
instance as above:
data Bar m r = ...
instance Cyc Bar where ...
instance (Num r, Foo m) => Num (Bar m r) where ...
instance EntailCyc Bar where
witNum _ = Dict
Then I define a newtype wrapper and give a generic Cyc
instance for it:
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
Finally, I change all functions that used a generic c m r
type to use a W c m r
type:
foo :: (Cyc c, EntailCyc c, Foo m, Num r) => W c m r -> W c m r
foo = (*2)
The point here is that foo
might need many constraints (e.g., Eq (W c m r)
, Show (W c m r)
, etc) that would each individually require their own constraints. However, the generic instances for W c m r
for Eq
, Show
, etc all have exactly the constraints (EntailCyc c, Foo m, Eq/Show/... a)
, so the constraints on foo
above are the only constraints I need to write!
这篇关于将字典变成约束的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!