Your first OCaml
What OCaml is, how a script runs from the first line to the last, and the two ways to write a value out: print_endline and print_string.
A compiled language with a small surface
OCaml comes from the ML family, the same tradition as Scheme and Haskell: values, functions and types, with a compiler that checks every expression before anything runs. let names a value, fun builds a function, and types are worked out rather than declared, so a very short program still carries a lot of checking.
This course runs the real compiler. Press Run and the file is compiled and executed; every example prints its own answers, so the output pane is the feedback you need. Nothing reads from the keyboard and nothing reaches the network, so each program brings its own data.
A program is a series of definitions
A file is read from top to bottom. A plain let binding computes its value once, when it is written, while let () = ... runs a piece of code whose answer nobody wants. The pattern () is the only value of the unit type, which is exactly what a printing action answers, and a semicolon sequences two of those actions. That is also why a later definition can use an earlier one but never the other way round.
Whole numbers are int and decimals are float, and the two never mix: 7 / 2 is 3, while 7.0 /. 2.0 is 3.5. string_of_int, string_of_float and string_of_bool turn each of them into text, which is what a printer needs.
What the compiler checks before it runs
The file is compiled, not interpreted line by line. The compiler reads the whole file, works out a type for every expression, and only then produces something that can run. That is why a mistake on the last line can stop the first line from printing: nothing executes until the whole file makes sense.
A top-level let () = ... is an expression of type unit. The () on the left is a pattern that matches the one and only unit value, so let () = print_endline "hi" says "run this, and do not keep the answer". A semicolon joins two of those actions inside one let, and it is not a terminator: it is a small operator that means "do this, then that".
Compilation also explains the errors. A message such as This expression has type int but an expression was expected of type string points at the place where two types meet, often an argument to print_endline or the two branches of an if. Read the type it expected first; the fix is usually to convert the value rather than to rearrange the line.
Shadowing, and why it is not assignment
A second let on the same name does not change the first binding; it adds a new one that hides it for the rest of the file. Code written above still refers to the old value, and so does any function defined before the second let — a function closes over the environment where it was written, not the one that exists when it is called.
That makes shadowing a tool rather than an accident. You can take a value apart, check it, and give the checked version the same name: let n = ... followed by let n = max 0 n reads as a sequence of refinements. Each line states a new fact about n, and no earlier line changes underneath you.
What a type error is telling you
A message such as This expression has type int but an expression was expected of type string is a meeting of two facts, and it names both. The first is what the compiler found at the expression; the second is what the surrounding code required. The fix is usually at the smaller of the two: convert the value, or change the expectation.
A message that points at a whole line rather than a word usually means the line is being parsed as something other than you intended — a missing semicolon, a missing in, or a bracket in the wrong place. Read the expression the compiler highlighted, then look one token to its left.
Three mistakes stop a first program before it prints anything: a missing ; between two printing actions inside one let, which makes the compiler read the second expression as part of the first; a number handed straight to print_endline, which wants a string and will not accept an int; and a let inside a sequence without in, as in let x = 2 followed by more work, which is a definition where an expression was needed. Add the semicolon, add string_of_int, and add the in.
Try these next
- Print a line that joins a name and a number with
^, usingstring_of_inton the number. - Use
print_stringtwice andprint_endlineonce so that three pieces appear on one line. - Work out
9 / 2and9.0 /. 2.0on paper, then print both and check your prediction.
Printing, arithmetic and types
let () = print_endline "one line"
let () = print_string "no break here, "
let () = print_endline "but here"
let () =
print_endline (string_of_int (2 + 3 * 4));
print_endline (string_of_float (1.0 /. 4.0))Run it, then make it yours
Press Run and read the six lines. Then change 6 to 9, print the answer as a float, and add a line that prints `2 + 3 * 4` with no brackets to see what the compiler says.
A small receipt in two styles
Run it and read the lines. Then change the item and the price, add a second item, and make the total line end with a full stop on the same line.
print_string writes without ending the line and print_endline writes and then breaks it. Both come from the standard library, so a script needs no import. Printf.printf is the third way to write, and it gets a chapter of its own.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does print_endline "hi" do?
