Representing arbitrary implementations of a style class in Haskell
I'm trying to get over years of working in the classic Java style inheritance model to really get comfortable with Haskell. This is not the case and I need help.
Suppose I have a typeclass Foo
. I want to present a list of arbitrary implementation classes Foo
, but not in such a way that every item in the list is the same; I want a heterogeneous public type so users of my library can implement their own Foo
.
The reason is that I would 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 do I create an extensible type (or another data type) that can be freely mixed with others of the same class, but not necessarily the same exact type?
source to share
What you are looking for, Heterogeneous collections
IMHO the best way is to use existences as described in the 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 ()) ]
source to share
The problem with what you are trying to do (heterogeneous) is that: after you have a list Foo
, it is difficult to revert to the original type. However, if you are happy to forget the original type, another way to solve your problem is to convert your data to Foo
. This approach may sound wrong, but remember that data is immutable in Haskell, so you can never change your objects, so the only thing you can do with yours Foo
is to get information from them. Then there is no difference between the real Bar
one acting as a Foo
, and the new Foo
one acting as the version Bar
will be (also, Haskell is lazy, so the conversion will only be done when needed).
Once you have this realized, you can go ahead and replace object
, but just a bunch of functions (as @chi link pointed out). Your code will be
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]
Note that even if somethingElse
it doesn't look like a function, but just a simple function, it will never be called (in this example). The result toFoo
can be seen as a bridge between Bar
and Foo
.
source to share