Wheat Wizard
Wheat Wizard

Reputation: 4219

Using different minimal complete definitions for different datatype constructors

I have a typeclass Transforming, with two complete definitions apply and fill. Here it is:

import Data.Set (Set, foldr, union, empty, map, singleton)
import Prelude hiding (foldr, map)

class Transforming t where
  apply :: (Ord a) => t a -> a -> Set a
  apply trans = fill trans . singleton
  fill  :: (Ord a) => t a -> Set a -> Set a
  fill trans = foldr union empty . map (apply trans)

(The exact specifics are more or less unimportant to the question, but I thought I would include them)

Now the way I have it set up to infer member functions is nice, but often naive. So if I am making an instance of Transforming I may want to implement either apply or fill depending on what is most efficient, and let Haskell handle the other. (Or in rare cases implement both) And this is fine that is kind of the point of having a typeclass. However at this point I would like to make a new datatype as follows

data SimpleTransform a = SingletonTrans (a -> a) | GenericTrans (a -> Set a)

Now when making this SimpleTransform an instance of Transforming I want to implement fill for instances constructed with SingletonTrans and implement apply for instances constructed with GenericTrans. I would like to write something like this:

instance Transforming Transform where
  fill (SingletonTrans f) = map f
  apply (GenericTrans f) = f

However this doesn't work. Since Transform is a single datatype I need to completely define one of the two minimal definitions rather than partially define both. Now there are ways around this problem. The simplest is to just fully define both instances, however this is a super simple example and as the number of complete definitions in my typeclass grows and the number of constructors grows this begins to require repeating lots of code.

Is there a way I could use different complete definitions for different constructors of the same datatype? Is there some way I ought to restructure my code in order to make this unnecessary?

Upvotes: 3

Views: 161

Answers (2)

rampion
rampion

Reputation: 89053

Another way, using Data.Functor.Sum

newtype SingletonTrans' a = SingletonTrans' (a -> a)
instance Transforming SingletonTrans' where
  fill (SingletonTrans' f) = map f

newtype GenericTrans' a = GenericTrans' (a -> Set a)
instance Transforming GenericTrans' where
  apply (GenericTrans' g) = g

instance (Transforming f, Transforming g) => Transforming (Sum f g) where
  fill (InL f) = fill f
  fill (InR g) = fill g
  apply (InL f) = apply f
  apply (InR g) = apply g

type SimpleTransform = Sum SingletonTrans' GenericTrans'

You can even use PatternSynonyms to make SimpleTransform act just like the original definition:

pattern SingletonTrans :: (a -> a) -> SimpleTransform a
pattern SingletonTrans f = InL (SingletonTrans' f)

pattern GenericTrans :: (a -> Set a) -> SimpleTransform a
pattern GenericTrans g = InR (GenericTrans' g)

Upvotes: 5

Daniel Wagner
Daniel Wagner

Reputation: 152707

Here is one way:

applyDef trans = fill trans . singleton
fillDef trans = foldr union empty . map (apply trans)

class Transforming t where
  apply :: (Ord a) => t a -> a -> Set a
  apply = applyDef
  fill  :: (Ord a) => t a -> Set a -> Set a
  fill = fillDef

instance Transforming Transform where
  fill (SingletonTrans f) = map f
  fill trans = fillDef trans
  apply (GenericTrans f) = f
  apply trans = applyDef trans

Upvotes: 5

Related Questions