11

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 ...

2 Answers 2

14

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.

Sign up to request clarification or add additional context in comments.

2 Comments

This looks also like a great approach to me. And I'm definitely trying Maybe :)
@Cubinator73 This is usually the way to go. The multiparameter typeclass approach with a functional dependency (or a single parameter typeclass with an associated type) makes sense if you're considering multiple monomorphic stack types.
7

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.

3 Comments

Before this I actually tried the same syntax, but only wrote instance Stack (List a). And I forgot to append FlexibleInstances to the pragma. Now it works, thank you :)
If every a admits at most a single b such that Stack a b has an instance, a functional dependency a->b can help the type inference machinery significantly. Otherwise, each call to pop :: a -> a is ambiguous: b can not be determined from the context of pop. (Type families could also be used, of course.)
@chi: IIRC you can add restrictions, such that there will be only one such b for an a. Will look it up later.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.