Functions & Types

Course 1 · Ch 2
Functions & Types
Every function secretly takes exactly one argument — the arrows in a type signature aren't decoration

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

add :: Int -> Int -> Int add x y = x + y

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

add :: Int -> Int -> Int -- really means: add :: Int -> (Int -> Int) -- a function taking one Int, RETURNING another function addFive = add 5 -- addFive :: Int -> Int — a genuinely new, real function addFive 3 -- 8

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

id :: a -> a id x = x

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.

AspectTypeScript (ts1-2)C# (csharp2-3)Haskell
Type inferencestructural, local, escape hatches (any)var, local onlyHindley-Milner — complete, most-general type
Multi-argument functionsa real, direct featurea real, direct featurea chain of one-argument functions (curried)
Partial applicationmanual wrapper neededmanual lambda wrapper neededautomatic, for every function, always
Use partial application instead of writing wrapper lambdas
Where another language needs a hand-written lambda to specialize a general function, Haskell's currying already provides it directly — add 5 alone is the specialized function, no wrapper required.
An arrow-chain signature is not "N inputs, one output" as a single unit
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

Challenge 1

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 solution
Challenge 2

In 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 solution
Challenge 3

Write 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 solution

Chapter 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