Cubi73
Cubi73

Reputation: 1951

Multiple type parameters in type class?

Lets assume I have a type class Stack with one instance List:

class Stack a where
    push :: a -> Integer -> a
    pop :: a -> a
    last :: a -> Integer

data List = Empty | Element Integer List
instance Stack List where
    push list value = Element value list
    pop Empty = error "No elements"
    pop (Element _ list) = list
    last Empty = error "No elements"
    last (Element value _) = value

How Stack has to be defined in order for List to not be limited to Integer values?

-- class Stack (?) where ...
data List a = Empty | Element a (List a)
-- instance Show (List a) where ...

Upvotes: 11

Views: 1363

Answers (2)

Daniel Wagner
Daniel Wagner

Reputation: 152682

Consider using a higher-kinded class variable. Thus:

class Stack s where
    push :: s a -> a -> s a
    pop  :: s a -> s a
    last :: s a -> a

data List a = Empty | Element a (List a)

The instance remains exactly as you wrote it (though List now has kind * -> * instead of *):

instance Stack List where
    push list value = Element value list
    pop Empty = error "No elements"
    pop (Element _ list) = list
    last Empty = error "No elements"
    last (Element value _) = value

This approach is pure Haskell 2010 -- it requires no extensions.

Also, consider making your failures observable; for instance by changing the type of pop and last to return Maybe (s a) and Maybe a, respectively.

Upvotes: 14

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476503

In that case you can make a multiparameter class:

class Stack a b where
    push :: a -> b -> a
    pop :: a -> a
    last :: a -> b

and define it with:

instance Stack (List b) b where --You don't need to use `b`, but this make it easier to understand
    push list value = Element value list
    pop Empty = error "No elements"
    pop (Element _ list) = list
    last Empty = error "No elements"
    last (Element value _) = value

Note that this is not a default (standardized) Haskell feature, and that you will need to turn it on. Either by passing -XMultiParamTypeClasses and -XFlexibleInstances to the compiler.

Or you can write:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}

in the header of your source file.


Note that there can be several b's for one a for which you define an instance (and vice versa). This can make it hard to work with such classes. Say for instance you write a Dummy type:

data Dummy = Dummy

you can define:

instance Stack Dummy b where
    push x = const x
    pop = id
    last = const $ error "Dummy object"

Now it means that you have Stack instance for every possible b, so that you can push and pop all kinds of stuff to Dummy objects.

Upvotes: 7

Related Questions