I'm writing a type where I want to be able to change the behavior of a method based on the typeclass instantiations of internal types.
As an example, consider this:
class Reduceable a where
reduce :: a -> a
-- Other methods not relevant here
instance Reduceable [[a]] where
-- Reduce a list by dropping empty lists in the front
reduce = dropWhile null
-- Other methods implemented
instance Reduceable [a] => Reduceable [[a]] where
-- Reduce a list of reduceable things by dropping empty lists in the front
-- and also reducing the elements of the list
reduce = dropWhile null . map reduce
-- Other methods identical to Reduceable [[a]]
In this case, I could use OverlappingInstances to make this code work. However, the other functions are somewhat involved in their implementation and do not change based on the type, and so I'd rather not have to implement them multiple times (which, by my understanding of overlapping instances, would be required).
Is there a way I can get what I want in Haskell?
In general, there's not a really great way to do what you ask. The slightly verbose but idiomatic way is to use newtype to select between multiple instances when such exist. For example, you could wrap your elements in a newtype for which reduce does no work:
newtype Don'tReduce a = Don'tReduce a
instance Reduceable (Don'tReduce a) where reduce = id
Now your instance Reduceable a => Reduceable [a] instance can be made to "bottom out" by converting its elements to [Don'tReduce a]s before calling reduce. This also gives you some flexibility about where you consider the bottom to be; for example, these may all behave differently, and any one of them may be reasonable in a specific situation:
reduce :: Reduceable a => [[[[a]]]] -> [[[[a]]]]
reduce :: [[[[Don'tReduce a]]]] -> [[[[Don'tReduce a]]]]
reduce :: [[[Don'tReduce [a]]]] -> [[[Don'tReduce [a]]]]
reduce :: [[Don'tReduce [[a]]]] -> [[Don'tReduce [[a]]]]
reduce :: [Don'tReduce [[[a]]]] -> [Don'tReduce [[[a]]]]
reduce :: Don'tReduce [[[[a]]]] -> Don'tReduce [[[[a]]]]
You can use coerce to make conversion between [[[[a]]]] and any of the above variation types cheap (free, with optimizations on) at runtime.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With