Reputation: 39556
Why cannot I write something like:
data Color = R | G | B deriving Show
showColor :: Show Color
showColor = Show Color
main = do
putStrLn (showColor.show R)
putStrLn (showColor.show G)
Why are instances of classes not first class citizens in Haskell?
Upvotes: 2
Views: 166
Reputation: 71515
The idea behind type classes is in the name: a type class is a class (or set) of types (which all support a given interface).
For example Eq
is the class of types that support equality.
A function with type Eq a => [a] -> Maybe a
is specifying that the type variable a
ranges over the types that are members of Eq
; this you need proof that your type is a member of Eq
before you can call this function.
An instance then is just a constructive proof that a particular type is a member of a class, by demonstrating how the type supports the interface.
Seen this way, it's not just that Haskell doesn't support multiple instances; the idea of multiple instances doesn't make sense. A type is in a class or it isn't; the notion of class/set allows you to enquire about membership, but not to store multiple different implementations.
Most critically, a function shouldn't be able to provide different return values for the same input, just because you used different membership proofs! And existing Haskell code does rely on this property: the classic example being the functions in Data.Map
which (almost) all have an Ord
constraint in order to operate on a Map
as a tree internally. If it were possible (and normal) to have many different instances of Ord
for a type and pass one of your choosing at each call, then this would be a wildly unsafe interface!
The practice of defining laws for a class that valid instances must conform to will also depend on this property. If I write a set of interrelated functioned that assume all instances of Monad
uphold the monad laws, it's quite likely that my code is broken if different instances can be provided to different calls operating on the same data.
Note that Scala does not have type classes. What it has is implicit parameters; there is a design pattern to implement type classes yourself using implicit parameters. But it's a leaky abstraction; you can use implicit parameters to do things that do not fit the conceptual framework of a "set of types".
Haskell implements type classes basically using implicit parameter passing, but it enforces the abstraction; instead of requiring all uses of implicit parameters that are for type classes to be selected coherently, Haskell rejects any situation where instances could be incoherent. The implicit parameters are an internal implementation detail; asking why they are not first class is a bit like asking why vtables are not first class in an OO language.
These days Haskell does explicitly support implicit parameter passing (with an extension), so you could probably use the same design pattern to implement "multiple instances" in code that you write. But code written with that facility is expecting the implicit parameters to be parameters, and should work when different values are passed arbitrarily. Code written using type classes expects that the type class abstraction is what's being used.
Upvotes: 0
Reputation: 116139
It's hard to discuss why a certain feature of the language is as it is, since only the designers of the language can really answer that. What however happened is that they decided that each type can have at most one instance for each class. This is likely to be driven by the fact that instances are meant to be used implicitly. Consider a class
class Foo a where foo :: a -> String
and this module
import A -- defines an instance Foo Int
bar :: Int -> String
bar n = "The number is " ++ foo n
The bar
function implicitly refers to module A
's instance. Now suppose we add another import
import A -- defines an instance Foo Int
import B -- defines another instance Foo Int
bar :: Int -> String
bar n = "The number is " ++ foo n
This is now ambiguous. Haskell might have provided a syntax to disambiguate this, but instead chose to disallow it. One advantage is that whoever reads the code can more easily find the used instances, since there can be only one.
Multiple instances can be partially simulated, though. A GHC extension allows one to define implicit parameters, allowing one to specify a different "instance" for those parameters at each function call:
{-# LANGUAGE ImplicitParams #-}
data Color = R | G | B
showColor :: (?showC :: Color -> String) => Color -> String
showColor c = "The color is: " ++ ?showC c
main :: IO ()
main = do
let ?showC = \c -> case c of R -> "Red" ; _ -> "Not Red"
in putStrLn (showColor B)
let ?showC = \c -> case c of G -> "Green" ; _ -> "Not Green"
in putStrLn (showColor B)
The output of the above is:
The color is: Not Red
The color is: Not Green
The Agda programming language does not have type classes, but has implicit instances which play a similar role. There, an instance is implicitly passed (as in Haskell) but if desired you can manually override the implicit argument and use a special syntax to specify a different instance.
Upvotes: 5
Reputation: 22616
Your question is hard to understand mainly because
showColour :: Show Color
showColour = Show Color
Doesn't really make sense. How can Show Color
be of type Show Color
?
Show
can be seen as a constraint, so ultimatly Show Color
is a Bool
and in our case will evaluate to True.
Anyway, static scoping can be achieved using module. You can easily do something like
module Color where
import qualified Prelude
data Color = ...
show :: Color -> String
show c = Prelude.show c
And then use print (Color.show R)
.
Upvotes: -1
Reputation: 62848
In Haskell, type classes are pretty implicit. (For example, you cannot explicitly export or not export a class instance — which is occasionally annoying.)
The whole point of a type class is that it's a bunch of functions where the correct one gets selected automatically. If you want to be able to pass in a different function each type — well, just do that!
For example, there's a class
class Monoid m where
mempty :: m
mappend :: m -> m -> m
So we could do something like
fold :: Monoid m => [m] -> m
fold ( []) = mempty
fold (x:xs) = x `mappend` fold xs
And yet, nobody does that. Instead, they explicitly pass in the parameters:
foldr :: (x -> y -> y) -> y -> [x] -> y
foldr f y0 ( []) = y0
foldr f y0 (x:xs) = f x (foldr f y0 xs)
Because that way, it's much easier to fold the same data-type with multiple different functions / start values.
If you want to be able to "show" a value multiple different ways, just pass the show function in as an argument. If you want the function to automatically pick the right show function for the data-type, use the Show
class — but in that case, there can be only one instance (because that's kind of the point).
Upvotes: 0