I have the following type class
class MyClass c where
aFunction :: c -> Bool
and two instances for two different data types
data MyDataType1 = MyDataType1
instance MyClass MyDataType1 where
aFunction c = True
data MyDataType2 = MyDataType2
instance MyClass MyDataType2 where
aFunction c = False
I want to write a function a function which takes two parameters of typeclass MyClass (which might be the same data type or might be different and returns one of them. I'm struggling to work out the type signature for this and I think I might be taking the wrong approach.
Would this be correct? If not what should I use instead?
chooseOne :: (MyClass a, MyClass b) => a -> b -> ?
chooseOne x y = if (aFunction x) then x else y
Your return value could be of either type, so the compiler will complain unless you use the same type for both, giving
chooseOne :: (MyClass a, MyClass a) => a -> a -> a
which isn't what you mean.
To combine two potentially different types into one, you can use the Either data type:
data Either a b = Left a | Right b
so you would have
chooseOne :: (MyClass a, MyClass b) => a -> b -> Either a b
chooseOne x y = if (aFunction x) then Right x else Left y
But I'd rather write that
chooseOne :: (MyClass a, MyClass b) => a -> b -> Either a b
chooseOne x y | aFunction x = Right x
| otherwise = Left y
The function you're writing is impossible in Haskell---the return type must be fixed and known at compile time. Thus, to write something like what you're interested in you need an Either
.
chooseOne :: (MyClass a, MyClass b) => a -> b -> Either a b
chooseOne x y = if (aFunction x) then Left x else Right y
Eventually, even in dynamic languages, you'd have to have some code which handles both the a
and b
types identically. This "eliminates" the Either
and is embodied in the function Data.Either.either
either :: (a -> c) -> (b -> c) -> Either a b -> c
either f _ (Left a) = f a
either _ g (Right b) = g b
For your particular case, since both a
and b
are instances of MyClass
, it feels like we can make a slightly more convenient elimination function
eitherOfMyClass :: (MyClass a, MyClass b) => (a -> b) -> Either a a' -> b
eitherOfMyClass f (Left a) = f a
eitherOfMyClass f (Right a') = f a'
But this actually won't type-check! If you look closely at the type you might be able to find the problem---the handler function we're passing in is specialized to a
and thus cannot be applied to the Right
side of your Either
which is type b
. We thus need to use forall
, an extension enabled by LANGUAGE RankNTypes
.
{-# LANGUAGE RankNTypes #-}
eitherOfMyClass :: (MyClass a, MyClass b) =>
(forall x. MyClass x => (x -> c)) -> Either a b -> c
eitherOfMyClass f (Left a) = f a
eitherOfMyClass f (Right b) = f b
This ensures that whatever function f
you pass in to eitherOfMyClass
is truly general to any instance of MyClass
and thus can be applied to both the a
and the b
in your Either
.
(See also “Haskell Antipattern: Existential Typeclass”.)
data MyType = MyType { aFunction :: Bool }
chooseOne :: MyType -> MyType -> MyType
chooseOne x y = if aFunction x then x else y
Caveat: your real MyClass
might not be simple enough to make this work.
n.b. If you could write it as a class in an OOP language, then you can use this technique. OOP constructors translate to free-standing functions that return a MyType
value.
Monoid
, mempty :: Monoid a => a
doesn't take a parameter so doesn't fit in this framework.Monoid
, mappend :: Monoid a => a -> a -> a
takes two parameters, and they may be of any type that implements Monoid
, but they must both be the same type that implements Monoid
; mappend
doesn't fit in this framework either.You could always do it backwards: instead of returning type x
or type y
, you could accept two functions as input and execute one or the other depending on what you wanted to "return":
chooseOne :: (x -> z) -> (y -> z) -> x -> y -> z
chooseOne f1 f2 x y = if aFunction x then f1 x else f2 y
Notice that if you do chooseOne Left Right
, you now have the Either
-based solution that some of the other guys have suggested. You could also do something like chooseOne show show
to return a String
as your result.
Whether this approach is better or worse depends on why you actually want to build this class in the first place (i.e., what your program is trying to do)...
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