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:

  • I'm wary of the generic instance head for Num in module Foo .
  • If any overlapping instances are imported into 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!

    链接地址: http://www.djcxy.com/p/43080.html

    上一篇: 什么是多态,什么是它,以及它是如何使用的?

    下一篇: 将字典变成约束