Functions, Types & Choices

Values are the nouns of your game. Today you write the verbs — functions that deal damage, size up dungeons and pick winners — and you meet the compiler's crystal ball: type inference.

You will be able to…

Teach the machine a new trick

In lesson 1.1 you named values: let lives = 3. A function is the same let, plus a parameter before the = — a blank that gets filled in later. Whatever the body evaluates to is what the function returns. No return keyword exists, or is needed:

Calling is just as minimal: double 25. A space, no parentheses, no commas. Here's the toplevel's side of the conversation:

Look at that answer line: int -> int. Read the arrow as a promise — "give me an int, I'll give you back an int". The <fun> just means "the value is code" — the toplevel won't print your recipe back at you.

And here's the eerie part: you never said n was an int. The compiler deduced it. * only works on ints, so n must be an int, so the result is one too. That detective work is called type inference, and it runs on everything you write, all the time, for free.

A "Double XP Weekend" begins! Write a function double that returns twice its argument.

Multiplication is *.
You don't need return — the last expression IS the result.

The crystal ball also guards the gate. Try to smuggle a string into double — run this and read the rejection letter slowly:

"This expression has type string but an expression was expected of type int." Decode it: this expression is "hi"; expected is what double's promise demands. Some languages would happily compute 2 * "hi" at 3 a.m. in a player's browser and crash the boss fight. OCaml refuses at compile time, while the bug is still cheap.

More ingredients, and pinning types down

Need two inputs? Stack the parameters:

int -> int -> int: one arrow per argument, and the final type is the result. Count arrows, know the shape — this one skill lets you read any OCaml library's documentation.

Inference usually gets it right on its own, but you can also annotate — state a type out loud. Wrap a parameter as (name : type), and put : type before the = for the result:

Annotations don't change what the function does. They document intent, and they give the compiler a place to point when something disagrees — error messages land closer to the real culprit.

Every dungeon room in our future games is a grid of tiles. Write area, taking a width and a height (both ints) and returning how many tiles the room holds. For style points, annotate the parameters and the result.

Two parameters, separated by spaces: let area w h = …
Multiply them — and if you fancy annotations: let area (w : int) (h : int) : int = w * h.

Choices: if/else is an expression

Comparisons produce bools: = (equal), <> (not equal), <, <=, >, >=. Combine facts with && (and), || (or), flip one with not. And here is the twist that makes OCaml OCaml: an if/else isn't a statement that does something — it's an expression that is something. It produces a value, so you can bind it, print it, or feed it to a function:

Because the whole if must have one type, both branches must agree: if hp > 0 then "fighting" else 0 is refused — a string from one door, an int from the other, and the compiler won't guess which you meant. Same reasoning explains why else is required here: without it, what would status be when hp is zero?

Often you don't even need if. A comparison already is a bool:

Read is_even inside-out: n mod 2 is the remainder after dividing by two; = 0 asks whether it's zero; that question's answer — a bool — is the result. Writing if n mod 2 = 0 then true else false works, but it's asking a yes/no question and then repeating the answer back to yourself.

Three heroes walk in with power levels a, b, c. Write max3 returning the highest of the three. Chained if/else is the intended path — though the stdlib's max is legal too.

Chain choices: if … then … else if … then … else …
To crown a it must beat both rivals: a >= b && a >= c.
if a >= b && a >= c then a else if b >= c then b else c — or cheat gloriously with max a (max b c).

Functions that call themselves

One more superpower and we'll call it a day. A function may use itself in its own body — if you warn the compiler with rec:

Trace it like a stack of quest scrolls: shout 3 is "Ha" ^ shout 2, which is "Ha" ^ "Ha" ^ shout 1… until shout 0 answers "" and the whole tower collapses into "HaHaHa". Every recursive function obeys two sacred rules: a base case that answers without calling itself, and recursive calls that step toward that base case. Break either rule and the function falls forever.

A combo chain pays 1 coin on the first hit, 2 on the second, 3 on the third… Write sum_to so that sum_to n returns 1 + 2 + … + n (and 0 for a whiffed combo, n = 0). Legend says Gauss summed to 100 instantly with a formula — your function gets to earn it the honest, recursive way.

The base case is already written: small n pays nothing.
The else adds n to a smaller sum: n + sum_to (n - 1) — parentheses around n - 1!
Deeper: how far can inference see?

Across your whole program. And when nothing constrains a parameter, OCaml doesn't guess — it generalises: let id x = x gets the type 'a -> 'a, where 'a (say "alpha") means any type you like, but the same one in and out. One function, infinitely many uses, still completely type-safe. This is why stdlib functions like max work on ints, floats and strings alike. You'll wield 'a yourself when lists arrive in Phase 2.

Checkpoint quiz

Loot collected

Next: we leave the browser for an hour and forge the real toolchain on your machine — then come back armed.