ZhekaKozlov
ZhekaKozlov

Reputation: 39556

Why cannot instances of classes be used as values?

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

Answers (4)

Ben
Ben

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

chi
chi

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

mb14
mb14

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

MathematicalOrchid
MathematicalOrchid

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

Related Questions