Turning a Dict into a constraint
I have a class Cyc cr which has functions for datas of the form cmr , 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 (cmr) a superclass constraint of Cyc . My intention is that cmr should be a Num whenever (Cyc cr, 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 (cmr) 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 (cmr) constraint to foo , but I'm trying to reduce the number of constraints, remember? (Cyc cr, Foo m) is supposed to imply a Num (cmr) constraint (and I need Cyc cr 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:
Num in module Foo . Foo , I suddenly need IncoherentInstances or the Num constraint on foo to defer instance selection to runtime. Is there an alternative way to avoid using Dict in every function that needs Num (cmr) 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 cmr type to use a W cmr 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 (eg, Eq (W cmr) , Show (W cmr) , etc) that would each individually require their own constraints. However, the generic instances for W cmr 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!
下一篇: 将字典变成约束
