I have two almost equal functions for different data constructors and would like to know if there is a possibility to unify both. A minimalistic example would be the following
f_maybe :: Maybe a -> a -> a
f_maybe (Just x) _ = x
f_maybe _ x = x
and
data T a = T1 a | T2 Int | T3
f_t :: T a -> a -> a
f_t (T1 x) _ = x
f_t _ x = x
Is the a way to define only one function parametrized by the type constructors (Maybe or T) and the data constructor (Just or T1) doing both?
Once you implemented Foldable
instance for your data type, this idea can be expressed as foldr const
.
data T a = T1 a | T2 Int | T3
instance Foldable T where
-- if it's T1, it has a value
foldMap f (T1 a) = f a
-- if it's T2 or T3, it's considered "empty"
foldMap f (T2 _) = mempty
foldMap f T3 = mempty
You can even let generic-deriving
package derive the above Foldable
instance for you:
{-# LANGUAGE DeriveGeneric #-}
import Generics.Deriving.Foldable
import GHC.Generics
data T a = T1 a | T2 Int | T3
deriving (Generic1)
instance Foldable T where
foldMap = gfoldMapdefault
(In your case the instance is not ambiguous since only one of the constructors may contain exactly one value of type a
. if the data type gets over-complicated with multiple constructors containing one or more a
, you'd better do it manually or check carefully what that package derived)
And some results from ghci:
-- `Maybe` already has the instance defined by the standard library
> foldr const 2 (Just 3)
< 3
> foldr const 2 Nothing
< 2
> foldr const 2 (T1 3)
< 3
> foldr const 2 (T2 10)
< 2
> foldr const 2 T3
< 2
You can write a unified function that has any behavior you want and works on any type of argument, but you cannot pattern match without concrete data constructor (polymorphicly), AFAIK.
Ask yourself what type signature of this unified function would look like?
The type signature represents idea behind function. What do Maybe
and T
have in common in this case so that you what to unify functions that work on
them? What kind of abstraction are you trying to build?
All these things are not clear from your question. If your idea is to just cut boilerplate (and this is not exactly boilerplate), then do not try to unify things that are different just to make code shorter, you will get something confusing in the end.
Types actually should be different and functions should reject arguments of wrong type, not work on anything that looks remotely similar. Too polymorphic code is often not a good idea, because wrong code may happen to have some meaning for type checker and then you will get program that compiles, but does wrong things.
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