Decisions are expressions
if/then/else answers a value rather than running a statement, why else is not optional, and how conditions are put together.
There is no if statement
In OCaml there is no statement that branches: if is an expression, and the whole thing has the value of the branch that was taken. That is why let bigger a b = if a > b then a else b is a complete function — the if is the answer, with no return anywhere.
When the branches only print, they both answer the unit value and the if is used as an action. When a value is needed, else is not optional: an if without it must answer unit, and the compiler enforces that rule rather than letting a missing branch decide the type.
Building a condition
Conditions are built from =, <>, <, <=, > and >=, joined with && for and, || for or, and not for negation. An if chains without a special form, so if a then b else if c then d else e is one expression, not a special case.
Equality is a single =. A double == also exists, but it asks whether two values are the same object in memory, which is almost never the question you mean for numbers, strings or records. For the same reason, compare returns an int rather than a bool: it is for sorting, not for asking a yes-or-no question.
Short-circuit, and the cost of a condition
&& and || do not evaluate both sides when the answer is already decided. logged_in && is_admin user never calls is_admin for a visitor who is not logged in, and cache_hit || fetch_from_disk key never fetches when the cache already has the answer. That is not an optimisation you have to ask for; it is how the operators are defined.
This matters when the right-hand side has an effect or can fail. n <> 0 && 100 / n > 5 is safe because the division is only reached when n is not zero. Put the cheap test first and the dangerous one second, and the condition reads as a guard rather than a gamble.
An if is an expression, so it can be the body of a function without a return, and it can sit inside a print_endline argument. But both branches have to answer the same type. A branch that prints and a branch that produces a number cannot be joined, because the compiler has to give the whole expression one type before it runs.
How the compiler types an if
The rule is small: type the condition, require it to be a bool, then type each branch and require the two types to be equal. The result is that shared type. A missing else is treated as an else (), which is why the whole expression becomes unit and why a value-returning if needs both branches.
This is also why the branches are not merged the way a reader might expect. The compiler does not pick the more general of the two; it requires them to be the same. if flag then 1 else "one" is refused rather than widened to a union, because OCaml has no implicit union of that kind. When the answers really differ, wrap them in a variant and the type says so.
What goes wrong
The most common error is a branch that prints where the other answers. if ok then print_endline "yes" else "no" fails because one branch is unit and the other is a string. If both branches should print, print in both; if one should answer, answer in both.
The second is a condition that is not a bool. if n then ... is refused, because an int is not a truth value; there is no truthiness in OCaml, so n <> 0 has to be written out. That extra comparison is the check, and it is why a zero can never sneak through as false.
Three mistakes are worth recognising: = is comparison, not assignment, so if x = 0 then ... is a test and x = 0 alone is a bool; a missing else makes the if answer unit, which is fine for printing and a type error when a value is needed; and comparing floats with = is unreliable after arithmetic, so compare the difference with a small tolerance instead.
Try these next
- Write
max3 a b cwith twoifexpressions, and check it against the largest of three numbers. - Build a condition for a number in 1..100 that is even, then test both boundaries.
- Rewrite a nested
ifas amatchon a tuple and compare which version reads more clearly.
A grade from a score
let grade score =
if score >= 90 then "A"
else if score >= 80 then "B"
else "C"
let () = print_endline (grade 85)A bigger number, a sign, a range
Run it and read the eight lines. Then change the boundary in `in_range` to 5, and rewrite `sign` so that zero counts as positive.
Ticket prices by age
Run it, then add a student band from 18 to 25 with its own price, and print one line that checks whether a child rides free.
Both branches of an if must have the same type, because the compiler has to know what the expression answers without running it. A branch that prints and a branch that answers a number is a type error, not a rule about style.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What value does if 2 > 3 then "yes" else "no" have?
