Ryan Kennedy
Ryan Kennedy

Reputation: 3647

Representing arbitrary implementations of a typeclass in Haskell

I'm trying to overcome years of working within the classic Java-style inheritance model to truly get comfortable with Haskell. It hasn't been going well, and I need a bit of help.

Suppose I have a typeclass Foo. I want to represent a list of arbitrary implementing classes of Foo, but not in a way that restricts every item in the list to be the same; I need to have a heterogeneous, open type so consumers of my library can implement their own Foo.

The reason why is because I'd like to write something like the following (pidgin Haskell):

class Foo -- something...

data Bar = Bar Int Char
data Baz = Baz String

instance Foo Bar
instance Foo Baz

saySomething :: Foo -> String
saySomething (Bar x _) = "Bar number " ++ (show x)
saySomething (Baz x) = "Your baz is " ++ x
saySomething _ = "Unknown Foo"

sayAll :: [Foo] -> [String]
sayAll = map (saySomething)

main = putStrLn $ sayAll [ (Bar 5 'k'), (Bar 7 'G'), (Baz "hello") ]

How would I create an extensible typeclass (or other datatype) that can be freely mixed with others of the same typeclass, but not necessarily the same exact type?

Upvotes: 3

Views: 94

Answers (2)

mb14
mb14

Reputation: 22636

The problem with what you are trying to do (heterogenous) collection is that : once you have a list of Foo it's hard to go back to the original type. However, if you are happy to forget the original type, an other way to solve your problem is to convert your data to Foo. This approach might seems wrong but remember, data are immutable in Haskell, so you'll never be able to modify your objects anyway, so the only thing you can do with your Foos is get information from them. There is then no difference between a real Bar acting like a Foo and a new Foo acting as the Bar version would (also, Haskell lazy, so the conversion will be only done if needed).

When you realized that, you can even go further and replace object but just a bunch of functions (as stated in @chi link). Your code becomes

 data Foo = { saySomething :: String, saySomethingElse :: String }
 -- Haskell is lazzy, so saySomething can be seen as function
 -- without argument

 class Fooable a where
       toFoo :: a -> Foo

 data Bar = Bar Int Char
 data Baz = Bar String

 instance Fooable Bar where
        toFoo (Bar i c) = { "Bar number : " ++ show i, "Bar char : " ++ [c] }
 instance Fooable Baz where
        toFoo (Baz s) = {"Your baz is " ++ s, "Nothing to add." }

 sayAll : [Foo] -> [String]
 sayAll = map saySomething

 bar = Bar 1 'a'
 baz = Baz "Bazzar"

 sayAll [toFoo bar, toFoo baz]

Please note, that even though somethingElse doesn't look like a function but it's just a plain function, it will never been called (in this example). The result of toFoo can be seen as a bridge between Bar and Foo.

Upvotes: 2

utdemir
utdemir

Reputation: 27266

What you're looking is Heterogenous collections

IMHO the best way is to use existentials, as described on wiki:

{-# LANGUAGE ExistentialQuantification #-}

data Showable = forall a . Show a => MkShowable a

pack :: Show a => a -> Showable
pack = MkShowable

hlist :: [Showable]
hlist = [ pack 3
        , pack 'x'
        , pack pi
        , pack "string"
        , pack (Just ()) ]

Upvotes: 2

Related Questions