Reputation: 3647
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
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 Foo
s 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
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