Functions & Types
haskell1-1's type signatures used a single arrow. This chapter explains what happens once there's more than one — and it's a genuine, structural surprise for anyone arriving from a language where "a two-argument function" is simply a fact, not a disguise.
Function Definitions & Type Signatures
A type signature reads left to right, each -> separating one more piece from the rest. This chapter is entirely about taking that arrow-chain seriously — it's the first real hint of what's coming.
Hindley-Milner Type Inference
GHC can infer almost any type without a single annotation, using an algorithm called Hindley-Milner. This is a genuinely stronger guarantee than ts1-2's own TypeScript inference: Hindley-Milner is complete for a large, well-defined core of the language — it always finds the single most general type an expression could have, with no ambiguity and no escape hatches needed. TypeScript's inference is real and useful, but structural, local, and leans on any/assertions when it can't fully work something out; Haskell's core inference doesn't have that kind of fallback because it doesn't need one.
Currying — Every Function Takes Exactly One Argument
Here's the reveal: add :: Int -> Int -> Int is really Int -> (Int -> Int) — a function that takes one Int and returns another function, which itself takes one Int and returns the final Int. Multi-argument functions don't actually exist in Haskell; every one of them is a chain of one-argument functions. add 5 genuinely produces a real, callable function — addFive — not a placeholder or a partial-application trick bolted on afterward.
Partial Application in Practice
Because currying is how every function already works, partial application isn't a special feature some functions opt into — any function can be partially applied, always, automatically. This is a real, structural difference from csharp2-3's own Func<> delegates, where building a specialized version of a general function means writing a new lambda wrapper by hand — Haskell just gives you the specialized function directly, for free, from the same definition.
Polymorphic Types — A First Look
Lowercase type variables (a) mark a genuinely polymorphic function — comparable in spirit to java2-1's own <T> generics, but without that chapter's own erasure-vs-reification question ever coming up the same way; Haskell's approach is closer to true parametric polymorphism. This is only a first look — full typeclass-based polymorphism arrives properly in Chapter 8.
| Aspect | TypeScript (ts1-2) | C# (csharp2-3) | Haskell |
|---|---|---|---|
| Type inference | structural, local, escape hatches (any) | var, local only | Hindley-Milner — complete, most-general type |
| Multi-argument functions | a real, direct feature | a real, direct feature | a chain of one-argument functions (curried) |
| Partial application | manual wrapper needed | manual lambda wrapper needed | automatic, for every function, always |
add 5 alone is the specialized function, no wrapper required.
Int -> Int -> Int is genuinely a chain of two one-argument functions, not shorthand for "takes two Ints, returns one." Reading it that way works for calling it normally, but breaks down the moment partial application enters the picture — worth internalizing the real structure early.
Coding Challenges
Write a function multiply :: Int -> Int -> Int with an explicit type signature, then use partial application to create a triple function that multiplies its argument by 3, without writing a lambda.
📄 View solutionIn GHCi, check the type of a partially applied function (e.g. :t (add 5)) and explain in a comment why its type is Int -> Int rather than something else.
📄 View solutionWrite a short comment explaining why Int -> Int -> Int is really Int -> (Int -> Int), and why this specific structure is what makes automatic partial application possible for every function.
📄 View solutionChapter 2 Quick Reference
- Hindley-Milner inference is complete for a large language core — always finds the most general type, no escape hatches needed, a stronger guarantee than ts1-2's own inference
- Every Haskell function secretly takes exactly one argument — Int -> Int -> Int really means Int -> (Int -> Int)
- Partial application is automatic for every function, always — no manual wrapper lambdas needed, unlike csharp2-3's own Func<> delegates
- Lowercase type variables (a -> a) mark genuine parametric polymorphism — a first look, full typeclass polymorphism comes in Chapter 8
- Next chapter: immutability and pure functions — no reassignment exists at all, not just opt-in