I have two haskell functions, which convert between two algebraic data types.
data Ab = A | B
data Cd = C | D
fromAb :: Ab -> Cd
fromAb A = C
fromAb B = D
toAb :: Cd -> Ab
toAb C = A
toAb D = B
But I would like to make a polymorph function, that takes both algebraic data types and converts between them.
foo A = C
foo B = D
foo C = A
foo D = B
But Haskell deduces from "foo A = C" that the function is
foo :: Ab -> Cd
I tried to make the data types instances of a class to make foo polymorph but it didn't work.
class Abcd a
instance Abcd Ab
instance Abcd Cd
foo :: Abcd a => a -> Ab
Any Ideas?
You can combine multiple types with an and (for example, a name is a String and another String ), or you can combine types with an or (for example, a Bool is a True data constructor or a False data constructor). Types that are made by combining other types with an and are called product types.
This is a type where we specify the shape of each of the elements. Wikipedia has a thorough discussion. "Algebraic" refers to the property that an Algebraic Data Type is created by "algebraic" operations. The "algebra" here is "sums" and "products": "sum" is alternation ( A | B , meaning A or B but not both)
One is of type (String,Int) , whereas the other is (Int,String) . This has implications for building up lists of tuples. We could very well have lists like [("a",1),("b",9),("c",9)] , but Haskell cannot have a list like [("a",1),(2,"b"),(9,"c")] .
Haskell has three basic ways to declare a new type: The data declaration, which defines new data types. The type declaration for type synonyms, that is, alternative names for existing types. The newtype declaration, which defines new data types equivalent to existing ones.
This is very natural with TypeFamilies
. You define a type-level function
type family Converted a
type instance Converted Ab = Cd
type instance Converted Cd = Ab
Then your signature becomes
foo :: a -> Converted a
If you just were fiddling with types you'd be done, but since you want to have different behavior on the value level (returning an A
from a C
and so on) we actually need to spread our cases across instances of a new type class:
class Convertable a where
foo :: a -> Converted a
instance Convertable Ab where
foo A = C
foo B = D
instance Convertable Cd where
foo C = A
foo D = B
(live demo)
Finally, you might consider making Converted
a closed type synonym family if using recent GHC, or make it "associated" by moving the instances inside the Convertable
instance declarations.
Well, the signature in your last code fragment there is still wrong. It wouldn't be foo :: Abcd a => a -> Ab
, since if a ~ Ab
then the function should be returning a Cd
, not an Ab
.
There are a few different ways of doing what you want. First, recognize that what you're trying to do is express a common set of behavior based not on a type, but on a relationship between two types. This is basically the purpose of a multi-parameter typeclass (which is probably the simplest way to accomplish this).
{-# LANGUAGE MultiParamTypeClasses #-}
data Ab = A | B
data Cd = C | D
fromAb :: Ab -> Cd
fromAb A = C
fromAb B = D
toAb :: Cd -> Ab
toAb C = A
toAb D = B
class Iso a b where
to :: a -> b
instance Iso Ab Cd where
to = fromAb
instance Iso Cd Ab where
to = toAb
EDIT: Note that my answer is completely equivalent to jberryman's, which uses type families. This is what I mean by "a few ways of doing what you want."
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