Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell polymorphic function to convert between algebraic data types

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?

like image 964
Paamayim Nekudotayim Avatar asked Aug 07 '14 21:08

Paamayim Nekudotayim


People also ask

How data types are combined in Haskell?

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.

What is an algebraic data type Haskell?

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)

Can lists in Haskell have different types?

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")] .

How are datatype in Haskell defined and created?

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.


2 Answers

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.

like image 77
jberryman Avatar answered Dec 07 '22 09:12

jberryman


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."

like image 45
Mark Whitfield Avatar answered Dec 07 '22 08:12

Mark Whitfield