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?

+3


source to share


2 answers


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 ()) ]

      

+2


source


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

.

+2


source







All Articles