Tutorial · ~15 min read
Haskell, for people who already know how to program
You came up through C++, Java, Scala, and Python. You know interfaces, abstract classes, constructors, and generics in your bones. The bad news? Haskell's evangelists love to make it sound alien. The good news? ~90% of it is a vocabulary shift. Let's translate, not re-learn.
is the same patterns you know, written with different syntax and new names.
actually changed: Haskell splits data and behavior into separate buckets.
is genuinely new: compiler-enforced purity and lock-free concurrency.
The one mental model that fixes the "brain fog"
OOP bundles data + behavior into one box (the class).
Haskell keeps three things in separate boxes:
Box 1 · the fields
data
"What does this thing hold?" — your struct/fields.
Box 2 · the contract
class
"What can be done?" — the interface (called a typeclass).
Box 3 · the glue
instance
"This data satisfies that contract" — wired up separately.
Read any Haskell file as: "Here are my fields, here is my interface, and here are standalone functions processing them." That's the whole trick.
The "class" and the "constructor"
In Python a class declares the fields and the constructor (__init__) together.
In Haskell you write a data type. The thing on the left of = is the type name;
the thing on the right is a plain constructor function that takes arguments and hands you back a value.
Wait — why is field access "pattern matching" instead of user.name?
.name on.
Instead you write a standalone function that takes a User apart. The (MakeUser name _)
on the left says "if you hand me something built with MakeUser, bind its first field to name and ignore the rest (_)."
It's destructuring — like name, age = some_tuple in Python, but baked into the function signature.
Records — the part that looks like Scala case classes
Writing positional pattern matches gets old. Haskell's record syntax names the fields and
auto-generates the getter functions for you. If you've used Scala case classes or Python
@dataclass, this is the same convenience.
user.name reads "value, then field." In Haskell name user reads "function applied to value."
Same getter, mirrored word order.
Interfaces & abstract classes → typeclasses
You cringed at "Haskell doesn't have interfaces, it has typeclasses." You were right to. Structurally, a typeclass is an interface. The only real difference is where the wiring happens:
- In Java/Python the data type inherits the interface — the contract is welded onto the class.
- In Haskell you declare the interface (
class), declare the data (data), then write a third block of glue (instance) that connects them.
The "a" is the implementer
In class JSONSerializable a where, the a is a placeholder for "whatever type implements this." It's literally the self type, written as a generic parameter.
Why separate the glue?
Because you can make a type you don't own implement an interface you don't own. That's like adding a Java interface to java.lang.String after the fact — impossible there, routine in Haskell.
class does not mean an OOP class. It means interface. Read class as "interface" every single time and the cringe disappears.
Generics — List<T> is just [a]
The angle brackets you know from Java/Scala (Box<T>) become lowercase type variables in Haskell.
A "bounded generic" (<T extends Comparable>) becomes a typeclass constraint written with =>.
a, b, c = type parameters (your T, U). The fat arrow => = "given these interface constraints, here's the real signature."
Everything left of => is a bound; everything right of it is the function type.
Collections & transformations
Python reaches for comprehensions or map/filter with lambdas.
Haskell passes functions natively — and offers a neat trick called partial application: (* 2) is "the function that multiplies by 2."
Error handling — goodbye None, hello Maybe
Python returns None and trusts you to check it (you won't, and you'll ship an AttributeError).
Haskell's Maybe type is Just value or Nothing — and the compiler
refuses to build until you've handled the empty case. It's Scala's Option / Java's Optional, but mandatory.
And when you need an error message, not just "nothing"?
Either: Left error for the failure path, Right value for success.
It's the typed, exhaustively-checked version of throwing an exception — the failure type is right there in the signature, so callers can't pretend it won't happen.
Think Scala Either / Rust Result.
The part that's genuinely new
You asked whether there's anything truly more advanced. Yes — two things that C++, Java, and Python can't cleanly do. This is where the academics earn their pedantry.
1 · Purity enforced by the compiler
In Python any function can secretly write a file, mutate a global, or print. In Haskell, if a signature says
Int -> Int, it is mathematically impossible for it to do anything but compute an Int from an Int.
To touch the outside world, a function must advertise it by returning an IO type.
Side effects become explicit, visible architecture — not hidden landmines.
2 · Software Transactional Memory (STM)
Haskell runs concurrent threads with no locks, mutexes, or race conditions by treating memory updates like database transactions: they commit atomically, or roll back and retry automatically.
Python's GIL and Java's synchronized blocks feel ancient by comparison. This is the one place where programming genuinely progressed.
The Rosetta Stone — quick reference
Filter the table to find the OOP term you're hunting for.
| OOP / Python concept | Haskell equivalent |
|---|---|
| Class (fields) | data T = ... |
| Constructor | MakeT a b |
| Field getter | record syntax { f :: T } |
| Interface / abstract class | class C a where |
| implements / extends | instance C T where |
| Method call | func value |
Generic <T> | lowercase a |
Bounded generic <T extends C> | (C a) => ... |
| map / filter / reduce | map / filter / foldr |
None / null | Maybe (Just / Nothing) |
| Exception / typed error | Either (Left / Right) |
| Side effects (I/O, print) | IO a |
| enum / sealed types | data T = A | B | C |
No rows match that search.
Self-check: did the fog clear?
Five quick questions. Pick an answer to see whether the mapping stuck.
You scored