λ Scala → Haskell · ch.4 · typeclasses
🔌 Chapter 4 · One name, many types

The given/using you
already know.

Typeclasses are exactly Scala's implicits and givens — a way to attach behavior to a type after the fact, without touching the type's definition. Except Haskell had them first, and builds the whole standard library on them.

You already do this in Scala. When you want ==, show, or a JSON encoder to work across many types without writing a new overload by hand, you reach for a trait plus a given. Haskell's typeclass is that exact pattern — and it's where the idea came from.

This chapter walks the whole mechanism, one Scala anchor at a time:

  • Defining a class — an interface keyed by a type, like trait Eq[A].
  • Writing instances — supplying the behavior for one type, like given Eq[Color].
  • Constraints (=>) — "you must supply an Eq," which is Scala's using / context bound [A: Eq].
  • The standard cast & deriving — the classes you get for free, and free instances from the compiler.

1One name, many types

You want == to compare two Ints, two Colors, two lists — one name, many types. You want show to turn any value into a String. You want + to add Ints and Doubles. Writing a separate function per type by hand is the thing nobody wants to do.

In Scala you solve this with a trait that declares the operation, plus a given instance per type, and you pull the right one in with using. That's ad-hoc polymorphism: one name, a different implementation chosen by type.

Haskell's original answer to the very same problem is the typeclass. A class declares the operations; an instance supplies them for one type; and when you call the operation, the compiler picks the matching instance from the type — no dictionary you pass by hand, no runtime lookup you write.

🔌
The mental model: a typeclass is a plug socket. The class is the shape of the socket (the methods); an instance is a device that fits it for one type. Call show x and the compiler plugs in the right instance based on x's type — exactly how Scala resolves a given.

2Defining a class

A class is an interface keyed by a type variable. Here's Eq, the "things that can be compared for equality" class:

class Eq a where (==) :: a -> a -> Bool

Read it as: "for any type a that is an instance of Eq, there is an operation (==) that takes two as and gives back a Bool." The a is the slot the type fills in — it's the A in Scala's trait Eq[A].

Side by side, the shapes line up almost word for word:

-- Scala
trait Eq[A]:
  def eqv(x: A, y: A): Boolean

-- Haskell
class Eq a where
  (==) :: a -> a -> Bool

The class declares what operations exist and their types. It does not say how — that's the instance's job, next.

3Instances — supplying the behavior

An instance says "here's how this type satisfies the class." It's Scala's given — the concrete implementation the compiler will reach for:

data Color = Red | Green | Blue

instance Eq Color where
  Red   == Red   = True
  Green == Green = True
  Blue  == Blue  = True
  _     == _     = False

Same thing in Scala is a given for that type:

given Eq[Color] with
  def eqv(x: Color, y: Color) = x == y

Now Red == Red works. The compiler saw two Colors, looked up the Eq Color instance, and used its definition of (==). Pick a type and a method below and watch that lookup happen.

Interactive · instance resolution Pick a type + method · watch the compiler find the instance
Type: Method:
pick a type and method

4Constraints — the => arrow

Once a function wants to use a class operation, it has to demand that the type supports it. That demand is written before a fat arrow =>. Look at elem, "is this value in the list?":

elem :: Eq a => a -> [a] -> Bool

The part before the =>Eq a — is the constraint. It reads: "for any a that has an Eq instance…" Everything after the => is the ordinary function type. Inside elem, because you promised Eq a, you're allowed to use ==.

This is precisely Scala's using clause, or its sugar, the context bound:

-- Scala — both mean the same
def elem[A](x: A, xs: List[A])(using Eq[A]): Boolean
def elem[A: Eq](x: A, xs: List[A]): Boolean

-- Haskell
elem :: Eq a => a -> [a] -> Bool

The constraint is not a value, not a return type, not a cast. It's a requirement on the caller: "supply an Eq for a, and in exchange I can compare." Flip the constraint on and off below and watch == get rejected, then accepted.

Interactive · the constraint gate Toggle the Eq a => requirement

5The standard cast

A handful of classes ship with the language and show up constantly. You already know their Scala cousins — these are the ones to recognize on sight:

  • Eq — equality. Gives you == and /=. (Scala's Eq / universal ==.)
  • Ord — ordering. Gives you compare, <, >, max, min, plus sorting. (Scala's Ordering[A].)
  • Show — render to String via show. (Scala's toString / a Show typeclass.)
  • Read — parse from a String via read. (The inverse of Show; roughly String => A.)
  • Enum — types you can step through: succ, pred, ranges like [1..10].
  • Bounded — has a minBound and maxBound, like Int's limits.
  • Num — numbers: +, -, *, fromInteger. Why a literal 5 can be any numeric type.

Most of these you'll never write by hand — the compiler can generate them, which is the next section.

6deriving — free instances

For the obvious classes, writing the instance by hand is busywork: equality on a sum type just compares constructors and fields; show just prints them. So Haskell lets the compiler write them for you. Tack deriving onto the data declaration:

data Color = Red | Green | Blue deriving (Eq, Show, Ord)

That one clause hands you a working ==, a working show, and a working compare (where Red < Green < Blue, by declaration order) — no instance bodies written. It's the same payoff as Scala's typeclass derivation (derives Eq, Show in Scala 3, or a macro/Mirror-based derive), just built into the data syntax.

Toggle the classes below and watch the deriving line — and which operations become available — update live.

Interactive · deriving toggle Check classes · see the line and the unlocked ops
⚠️
Deriving needs the parts to qualify too. To deriving Ord a type, every field's type must itself be Ord — the compiler builds the instance out of its pieces. If a field has no Ord instance, you get a "no instance for…" error, the same way an unresolved given fails in Scala.

7Superclasses, defaults & the combiner pair

Two refinements round out the picture, then a teaser for later chapters.

Superclasses. A class can require another class first. Ord is declared as:

class Eq a => Ord a where compare :: a -> a -> Ordering

The Eq a => there means "you can only be Ord if you're already Eq" — ordering needs equality. That's a superclass requirement, the same idea as a Scala trait extending another (trait Ord[A] extends Eq[A]).

Default methods. A class can give some methods a default body, so an instance only has to supply the rest. Eq defines both == and /= in terms of each other, so you write just one — the minimal complete definition. Same as a trait with concrete methods you can but needn't override.

The everyday combiner: Semigroup and Monoid. One pairing you'll meet immediately is "things that combine." Semigroup gives you an associative combine operator (<>); Monoid adds an identity element mempty:

[1,2] <> [3,4]  -- => [1,2,3,4] (lists combine)
"ab" <> "cd"    -- => "abcd" (strings combine)
mempty :: [Int]  -- => [] (the identity)

If you've used Scala's Semigroup/Monoid from cats, this is the same shape — and we'll lean on it once we hit foldable structures. For now, just clock that (<>) is "combine" and mempty is "the empty thing."

💡
A superclass constraint flows automatically: if a function says Ord a =>, you also get Eq a for free inside it, because every Ord is an Eq. You never have to write both.

8The same idea, in both languages

Side by side: a Show class, an instance for one type, and a function that requires the class. Notice how given/using maps onto instance/=> one-to-one.

9Check yourself

3 questions · instant feedback 0 / 3

10Where this goes next

You can now read any signature with a => in it, write your own classes and instances, and let the compiler derive the boring ones. The classes ahead — Functor, Applicative, Monad — are just more typeclasses, with one twist: they're keyed by a type constructor (a "box") rather than a plain type. Next up, the first of them.

📦
Up next · Chapter 5
Functor & Applicative
map over anything in a box — and then apply boxed functions.