OCaml flashcards: the language and its tools
Recall cards for every lesson of the OCaml track, from the toplevel, dune and pattern matching to modules, effects, domains, the runtime and Dream.
347 cards. Read the question, answer it in your head, then open the card to check.
-
What kind of language is OCaml?
A statically typed language with type inference, garbage collection and modules: the compiler checks every type before the program runs and works most of them out without annotations.
From the lesson What OCaml is and where it is used
-
Why is sq "7" refused when sq squares an int?
OCaml never converts a value from one type to another for you, so the string is rejected before the program runs. Convert it yourself: sq (int_of_string "7").
From the lesson What OCaml is and where it is used
-
What do the commands ocaml, ocamlc and ocamlopt do?
ocaml starts the interactive toplevel, ocamlc compiles to portable bytecode that ocamlrun executes, and ocamlopt compiles to native machine code for one platform.
From the lesson What OCaml is and where it is used
-
How does an OCaml program become JavaScript or WebAssembly?
js_of_ocaml and wasm_of_ocaml take the bytecode that ocamlc produced and translate it into JavaScript or WebAssembly.
From the lesson What OCaml is and where it is used
-
How wide was the same int natively, in bytecode, under JavaScript and under WebAssembly?
63 bits natively and in bytecode, 32 bits under JavaScript and 31 bits under WebAssembly. A program has to know which back end runs it.
From the lesson What OCaml is and where it is used
-
What did OCaml 5 bring, and which release do this track's examples run on?
Domains for parallelism and effect handlers, with new syntax and types in the later 5.x releases. Every example in the track runs on OCaml 5.5.1.
From the lesson What OCaml is and where it is used
-
How do ocamlc and ocamlopt differ in what you get?
ocamlc compiles fast to bytecode that runs anywhere ocamlrun does and feeds the web back ends; ocamlopt builds faster native code for one platform, the build you ship. Both type-check alike and give 63-bit ints on a 64-bit machine.
From the lesson What OCaml is and where it is used
-
What is an opam switch?
One OCaml compiler together with its own installed packages, kept apart from every other switch on the machine. opam switch create 5.5.1 makes a global one and opam switch list shows them all.
From the lesson Install OCaml 5.5.1 with opam and use the REPL
-
Where does a local opam switch live, and how is it created?
In the project folder's _opam directory. Run opam switch create . 5.5.1 inside the project, so the project carries its own compiler and packages.
From the lesson Install OCaml 5.5.1 with opam and use the REPL
-
How do you read the toplevel's answer to a phrase?
End the phrase with ;;, then read left to right: the name (a dash when the value has none), the type after the colon and the value after the equals sign.
From the lesson Install OCaml 5.5.1 with opam and use the REPL
-
What do the directives #use, #trace, #show and #require do?
#use "file.ml" runs a source file as if typed, #trace f prints each call and return of f, #show prints a name's type or signature, and after #use "topfind", #require loads an installed library. utop accepts them too.
From the lesson Install OCaml 5.5.1 with opam and use the REPL
-
How do eval $(opam env) and opam exec -- differ?
eval $(opam env) puts a switch on the current shell's PATH; opam exec -- runs one command in a switch, and --switch=5.5.1 names which one, which suits scripts.
From the lesson Install OCaml 5.5.1 with opam and use the REPL
-
What did opam 2.6.0 change about the shell hook and PATH?
The hook now updates opam's folder in PATH where it already is instead of always moving it to the front, so a folder you put first stays first. The release also deletes build folders sooner.
From the lesson Install OCaml 5.5.1 with opam and use the REPL
-
Which macOS trap breaks a source-built OCaml 5.5.1?
Built with the macOS 27 SDK, it crashes in Unix.pipe. Building it with the macOS 26 SDK avoids the crash until 5.5.2 fixes it.
From the lesson Install OCaml 5.5.1 with opam and use the REPL
-
What does dune init project NAME create?
A library in lib/, an executable in bin/ and a test in test/, each with its own dune stanza; the executable and the test both use the library.
From the lesson Your first dune project and editor setup
-
What do dune build, dune exec and dune test do?
dune build compiles everything into _build/ and prints nothing when all is well, dune exec NAME builds and runs the named program, and dune test builds and runs the tests and fails if one fails.
From the lesson Your first dune project and editor setup
-
Why does a fresh dune project refuse to build while a variable is unused?
The default dev profile turns many warnings into errors. Remove the variable or name it _y to mark it unused on purpose; switching to the release profile only hides the warning.
From the lesson Your first dune project and editor setup
-
How should code be split between a dune library and its executable?
Put the code in the library and keep the executable thin, so the tests use the same library and dune does all the compiling.
From the lesson Your first dune project and editor setup
-
What do ocaml-lsp-server and dune fmt give you?
ocaml-lsp-server shows types and errors in the editor as you type. dune fmt runs ocamlformat, which formats only in a project whose .ocamlformat names the installed version.
From the lesson Your first dune project and editor setup
-
How do you compile one file without dune, and what does it leave behind?
ocamlfind ocamlopt or ocamlfind ocamlc; they leave the .cmi, .cmo, .cmx and .o files that dune keeps inside _build/. The .cmi is the module's compiled interface.
From the lesson Your first dune project and editor setup
-
What is every piece of an OCaml program, as the toplevel shows it?
An expression with a type and a value; the toplevel prints both for each phrase you enter.
From the lesson Expressions, let bindings, if and scope
-
A function reads x after let x = 10; later comes let x = x + 1. What does the function return?
Still 10. The second let makes a new binding that hides the first one from later code only; code written earlier keeps the binding it saw.
From the lesson Expressions, let bindings, if and scope
-
If a let binding never changes, how does OCaml code change state?
Explicitly: through ref cells, arrays and records with mutable fields.
From the lesson Expressions, let bindings, if and scope
-
Why must both branches of an if have the same type?
if … then … else is an expression with a single type. An if without else is allowed only when its then branch has type unit.
From the lesson Expressions, let bindings, if and scope
-
How do you make two calls depend on one if condition?
Wrap them in begin … end: if late then begin print_string "Late. "; print_endline "Fine is 50." end. Without it only the first call is inside the branch.
From the lesson Expressions, let bindings, if and scope
-
In what order are tuple components, function arguments and list elements evaluated?
The order is unspecified. When two effects must happen in a given order, bind each result with its own let … in, in that order; a sequence a; b also runs its expressions in order.
From the lesson Expressions, let bindings, if and scope
-
Why does 3 + 2.0 not compile?
int and float are separate types with separate operators: + takes ints and +. takes floats. Write the conversion yourself, as in float_of_int 3 +. 2.0.
From the lesson Integers, floats and arithmetic
-
What does max_int + 1 give in native OCaml 5.5.1?
min_int, -4611686018427387904, with no error: a native int has 63 bits and wraps silently.
From the lesson Integers, floats and arithmetic
-
What do you use when 63 bits are not enough, or a fixed width is required?
Int64 or Int32 for fixed widths, or an arbitrary-precision library such as Zarith. Under JavaScript and WebAssembly a plain int has only 32 or 31 bits.
From the lesson Integers, floats and arithmetic
-
Why is -7 mod 2 equal to -1, and how should oddness be tested?
Integer division truncates towards zero and mod takes the sign of the dividend. Test oddness with n mod 2 <> 0, and use Int.erem (OCaml 5.5) for a remainder that is never negative.
From the lesson Integers, floats and arithmetic
-
Which OCaml 5.5 functions round an integer division down or up?
Int.fdiv rounds down and Int.cdiv rounds up; Int.ediv and Int.erem keep the remainder non-negative.
From the lesson Integers, floats and arithmetic
-
Why is x = nan always false, and what should you write instead?
Floats follow IEEE 754, where nan equals nothing, not even itself. Write Float.is_nan x, or compare with Float.equal when nan must equal itself.
From the lesson Integers, floats and arithmetic
-
How do you read a number a user typed without stopping the program on bad input?
match int_of_string_opt line with Some age -> … | None -> …; plain int_of_string raises on input such as 12a.
From the lesson Integers, floats and arithmetic
-
Why can "abc" == "abc" be false while "abc" = "abc" is true?
The operator = compares contents (structural equality), while == is true only for the very same value in memory (physical equality). Two strings written separately are equal but not identical.
From the lesson Booleans, characters, strings and bytes
-
How should code compare what a user typed with "yes"?
With input = "yes" or String.equal input "yes". The operator == stays false for two different strings, even with the same characters.
From the lesson Booleans, characters, strings and bytes
-
What is an OCaml string, and when do Bytes and Buffer fit instead?
An immutable sequence of bytes, safe to share. Bytes changes bytes in place, and Buffer builds long text without copying it at every step.
From the lesson Booleans, characters, strings and bytes
-
Why is String.length of a six-character greeting in Devanagari 18?
It counts bytes, and each of those characters takes three bytes in UTF-8. Count and cut text by decoded characters with String.get_utf_8_uchar and the Uchar module.
From the lesson Booleans, characters, strings and bytes
-
When is the right operand of && or || evaluated?
Only when the left operand does not already decide the result: both operators short-circuit.
From the lesson Booleans, characters, strings and bytes
-
Which recent String functions handle everyday text without index arithmetic?
OCaml 5.5's String.split_first, split_all and replace_all, and 5.4's String.edit_distance.
From the lesson Booleans, characters, strings and bytes
-
How does OCaml read the type int -> int -> int?
As int -> (int -> int): every function takes one argument, so a function of two arguments returns a function waiting for the second.
From the lesson Functions, currying and the pipeline operator
-
With let add x y = x + y, what is add 3?
A new function still waiting for the second number. Partial application fills the first arguments now and returns a function for the rest.
From the lesson Functions, currying and the pipeline operator
-
Why is add (3, 4) a type error for a curried add?
The brackets and comma build one tuple, a value of another type. Separate arguments with spaces: add 3 4.
From the lesson Functions, currying and the pipeline operator
-
How should you order a function's parameters so partial application helps?
From the one that changes least to the one that changes most, so the varying argument comes last and the partly applied function fits List.map.
From the lesson Functions, currying and the pipeline operator
-
What does a closure keep, and what can still change it?
The bindings that existed when it was made; a later let of the same name does not reach it, but a ref it captured is shared and can change.
From the lesson Functions, currying and the pipeline operator
-
What do x |> f and f @@ x mean?
Both mean f x: the |> operator writes a chain of calls in the order they run, and @@ drops the brackets around a last argument.
From the lesson Functions, currying and the pipeline operator
-
How do you pass multiplication as a function, and why the spaces?
Write ( * ), as in List.map (( * ) 2), because (* opens a comment.
From the lesson Functions, currying and the pipeline operator
-
What decides how a custom operator groups and binds?
Its first character, which puts it in a row of the manual's precedence table: an operator that starts with - groups to the left, like subtraction.
From the lesson Functions, currying and the pipeline operator
-
Where do the types the toplevel prints come from?
OCaml infers each one from how the values are used, and leaves a type variable such as 'a where nothing forces a particular type.
From the lesson Type inference, polymorphism and type errors
-
What does the type 'a -> 'a describe?
A function that returns a value of the same type it receives, whatever that type is, as let id x = x does.
From the lesson Type inference, polymorphism and type errors
-
Why can (id 1, id "one") type-check when a parameter cannot be used at two types?
A let binding is generalised, so each use of id picks its own type for 'a; a function parameter keeps one type throughout the body.
From the lesson Type inference, polymorphism and type errors
-
What is a weak type variable such as '_weak1, and how is it removed?
A single unknown type left by the value restriction, which the first use fixes for good. Write the parameter out, or annotate the type you mean.
From the lesson Type inference, polymorphism and type errors
-
What does OCaml 5.5 accept in let pair_of (f : 'a. 'a -> 'a) = (f 1, f "one")?
A parameter declared polymorphic: pair_of Fun.id gave (1, "one"). Without the 'a., the annotation's 'a stands for one type.
From the lesson Type inference, polymorphism and type errors
-
Why can a type error point at a line that looks correct?
It marks where two types met, which may come after the real mistake. Trace the "actual" type back to where it was decided, or annotate the function so the message moves to the cause.
From the lesson Type inference, polymorphism and type errors
-
What do labels give a call of div ~num ~den?
Arguments named by their labels, so div ~num:10 ~den:2 and div ~den:2 ~num:10 both give 5. ~num alone passes a variable called num.
From the lesson Labelled and optional arguments
-
Which arguments are worth labelling?
Arguments of the same type whose roles differ, such as ~num and ~den or ~src and ~dst. Leave the one main argument unlabelled so the function still fits List.map and |>.
From the lesson Labelled and optional arguments
-
What does ?(punct = "!") declare in a parameter list?
An optional argument whose default is used when the call leaves it out. Without a default, the function receives None instead.
From the lesson Labelled and optional arguments
-
What is the difference between ~punct:v and ?punct:opt in a call?
~punct:v passes a value, ?punct:opt passes an option. The second is how a function forwards an optional argument it received, so the callee's default still applies.
From the lesson Labelled and optional arguments
-
When does an optional argument fall back to its default, and why do such functions end in ()?
Only when a positional argument after it is applied. With the optional argument last, every call returns a function (warning 16), so a final unit parameter is added.
From the lesson Labelled and optional arguments
-
How do List and ListLabels differ for labelled functions?
List functions take no labels and ListLabels functions do; a function with a required label must have it applied before it is passed to List.map.
From the lesson Labelled and optional arguments
-
What do let rec and the keyword and do in a function definition?
let rec lets a function call itself, and the keyword and joins functions that call each other. Each needs a base case that answers without recursing.
From the lesson Recursion, tail calls and the stack
-
What goes wrong with let rec count n = n + count (n - 1)?
It has no case that stops, so it calls itself until the stack runs out. Add one: if n = 0 then 0 else n + count (n - 1).
From the lesson Recursion, tail calls and the stack
-
What is a tail call, and what does OCaml do with one?
A call whose result the caller returns directly, with nothing left to do after it. OCaml reuses the current frame, so a tail-recursive function runs in constant stack space.
From the lesson Recursion, tail calls and the stack
-
Why is x + sum xs not a tail call, and what is the usual fix?
The addition waits for the recursive result, so every element needs a frame. An accumulator argument carries the partial result and makes the recursive call the last step.
From the lesson Recursion, tail calls and the stack
-
How far does the OCaml 5 stack grow, and what happens past the limit?
Up to the limit l of OCAMLRUNPARAM, 134217728 words by default; deeper recursion raises Stack_overflow.
From the lesson Recursion, tail calls and the stack
-
How can a test catch recursion that is not tail-recursive before a user's data does?
Run the tests with OCAMLRUNPARAM=l=100000, so a long input overflows in the test instead of hiding behind the large default limit.
From the lesson Recursion, tail calls and the stack
-
What do [@tailcall] and [@tail_mod_cons] ask of the compiler?
[@tailcall] asks it to check that a call is a tail call (warning 51 if not); [@tail_mod_cons] makes constructor-building recursion such as map tail-recursive (warning 71 if it cannot).
From the lesson Recursion, tail calls and the stack
-
How do you create, read and change a ref?
let r = ref 0 creates an int ref, !r reads it, r := 5 replaces the contents, and incr r or decr r adds or subtracts one. Each write returns ().
From the lesson Refs, loops and mutable state
-
Why does the toplevel print a ref as {contents = 0}?
A ref is a record with a single mutable field named contents.
From the lesson Refs, loops and mutable state
-
What does if !finished then … test?
Whether the ref holds true: the prefix ! reads a ref and is not negation. For "not finished", write if not !finished then ….
From the lesson Refs, loops and mutable state
-
What type do for and while loops have, and where does their changing state live?
Type unit, with an immutable index, so changing state lives in refs, arrays or buffers. When the job is to combine a list into one value, a fold says it better.
From the lesson Refs, loops and mutable state
-
Why does let backup = marks not keep an array's old order through a sort?
Both names point at the same array, so both see it sorted. Array.copy marks makes a new array with the same elements.
From the lesson Refs, loops and mutable state
-
How does a record field change in place?
Declare it mutable and assign it with <-. A functional update { r with … } still builds a copy.
From the lesson Refs, loops and mutable state
-
How can a function use mutation and still look pure to its callers?
Create the mutable state inside each call and return a plain value. A function of type … array -> unit, by contrast, exists to change its argument.
From the lesson Refs, loops and mutable state
-
Why does let (hi, lo) = min_max [42; 7; 19] compile even when hi ends up 7?
A tuple's positions have no names, so a pattern in the wrong order is accepted when the parts share a type. A record or a labelled tuple keeps the names with the values.
From the lesson Tuples, records and labelled tuples
-
What does { p with y = 10 } produce?
A copy of the record p with the field y replaced; p itself is unchanged.
From the lesson Tuples, records and labelled tuples
-
What does let { x; y } = p do?
It puns the field names: x and y become variables bound to those fields of p.
From the lesson Tuples, records and labelled tuples
-
Which type does an unannotated field pick when two record types share its name?
The most recently declared type that has it. Annotate the record or qualify the field with its module whenever types share field names.
From the lesson Tuples, records and labelled tuples
-
Why keep money in an integer of the smallest unit rather than a float?
Integers add and compare exactly, while a float such as 0.1 has no exact binary form, so a sum of many amounts drifts. Convert for printing only, with / 100 and mod 100.
From the lesson Tuples, records and labelled tuples
-
What do labelled tuples, new in OCaml 5.4, add?
Names on positions that are part of the type, such as lo:int * hi:int, so swapped values are caught without declaring a record type. Older compilers reject the syntax.
From the lesson Tuples, records and labelled tuples
-
Where may the labels of a labelled tuple be reordered or left out?
In a match or a let … in on a value whose type is known. Expressions and top-level let definitions keep the type's order.
From the lesson Tuples, records and labelled tuples
-
What is a variant type?
A type that lists every case a value can be as a constructor. Constructors may carry values, and they are not functions.
From the lesson Variants, options and recursive types
-
What does warning 8 report, and what happens if you ignore it?
A match that misses a case, with an example value. That value raises Match_failure at run time, as the warning predicted.
From the lesson Variants, options and recursive types
-
What is option, and why is it used where other languages use null?
The variant type 'a option = None | Some of 'a. It puts a missing answer in the type, so the compiler makes the caller deal with None before using the value.
From the lesson Variants, options and recursive types
-
How can a caller use an option's value?
With a match, or with Option.value ~default and Option.map. List.nth_opt [10; 20] 5, for example, returns None.
From the lesson Variants, options and recursive types
-
What makes a type such as 'a tree recursive and parameterised?
Its constructors refer to the type itself (a node holds subtrees) and 'a lets it hold values of any one type.
From the lesson Variants, options and recursive types
-
What happens when a search tree is built from sorted input?
It ends up as deep as a list, with one node on each level.
From the lesson Variants, options and recursive types
-
How is a function over a recursive type best written?
One case per constructor and one recursive call per sub-value of the type itself; the exhaustiveness check then points at any case a new constructor leaves out.
From the lesson Variants, options and recursive types
-
What do or-patterns, as-patterns and guards each add to a match?
An or-pattern matches either side, an as-pattern binds the whole matched value, and a guard adds a when test after the pattern. Cases are tried from the top until one matches.
From the lesson Pattern matching in depth
-
Why is | (0, b) | (a, 0) -> … refused?
Each side of an or-pattern has to bind the same variables. Write | (0, _) | (_, 0) -> …, or two separate cases.
From the lesson Pattern matching in depth
-
Why do the cases 0, n when n > 0 and n when n < 0 still draw warning 8?
The exhaustiveness check reads patterns, not guards, so only 0 counts as covered. End the match with an unguarded case such as | _ -> "negative".
From the lesson Pattern matching in depth
-
What do warnings 8, 11 and 4 each point at?
8: a value no case matches; 11: a case that can never run; 4 (off by default): a catch-all that will swallow constructors added later.
From the lesson Pattern matching in depth
-
Why write every constructor instead of _ for a type that will grow?
Then the next constructor you add makes warning 8 point at each match that has to handle it.
From the lesson Pattern matching in depth
-
How does a match … with exception case differ from try … with?
It catches an exception raised while computing the matched value only; try … with catches one from anywhere inside, the branches included.
From the lesson Pattern matching in depth
-
What does the function keyword do?
It defines a function that matches its single argument against cases directly, without naming the argument first.
From the lesson Pattern matching in depth
-
What is an OCaml list built from?
Either [] or a value joined to a list with ::. Each :: builds one immutable cell, so the new list shares the tail it was built on.
From the lesson Lists and list patterns
-
What does l1 @ l2 copy, and what does appending one element at a time cost?
Every cell of l1, while l2 is shared, so appending element by element costs about n²/2 cells; consing with :: and one List.rev costs 2n.
From the lesson Lists and list patterns
-
How much memory did 10,000 numbers cost when appended with @, against consing and reversing once?
150,015,000 words with @ against 60,000 with :: and one List.rev: the same list, about 2,500 times the allocation.
From the lesson Lists and list patterns
-
Which two cases does every list function need, and what does a forgotten one cause?
[] and x :: rest; leaving out [] draws warning 8. Patterns such as [x] and x :: (y :: _ as rest) name more elements.
From the lesson Lists and list patterns
-
Which list functions raise when there is no answer, and what replaces them?
List.hd, List.nth, List.find and List.assoc raise; nth_opt, find_opt and assoc_opt return None, which suits answers that may normally be missing.
From the lesson Lists and list patterns
-
Since when do List.take and List.drop exist, and what does a negative count do?
Since OCaml 5.3, where a negative count raised Invalid_argument; from 5.4 it takes nothing and drops nothing.
From the lesson Lists and list patterns
-
Which pair does an association-list lookup return, and when is it the wrong tool?
The leftmost pair with that key, so a pair added at the front hides an older one. It suits small tables; Map or Hashtbl suit large ones.
From the lesson Lists and list patterns
-
What type suits a function such as last that has no answer for []?
'a list -> 'a option, so the caller handles the empty case where it knows what to do.
From the lesson Lists and list patterns
-
Which List functions replace the usual hand-written recursions over a list?
map, filter, partition, filter_map and concat_map. They keep the order of the elements and never change the list they are given.
From the lesson Map, filter and fold
-
When is List.filter_map better than a map followed by a filter?
When a step can fail, as parsing can: the function returns an option, and the result has no None left to forget.
From the lesson Map, filter and fold
-
What do List.fold_left ( - ) 10 [1; 2; 3] and List.fold_right ( - ) [1; 2; 3] 10 give?
4 and -8. fold_left combines from the first element, fold_right from the last, so an order-sensitive function gives two answers.
From the lesson Map, filter and fold
-
Which fold suits a list of a million elements, and why?
fold_left: its recursive call is a tail call, so it needs constant stack space, while fold_right needs one stack frame per element.
From the lesson Map, filter and fold
-
When can two List.map passes be fused into one?
When both functions are pure: one pass then gives the same list without building one in the middle. With effects, such as saving after a parse that may raise, fusing changes what happens before a failure.
From the lesson Map, filter and fold
-
Why write a list computation as a |> pipeline of map, filter and fold?
It lists the steps in the order the data takes them, each stage is a small testable function, and the result equals the nested calls.
From the lesson Map, filter and fold
-
What does a fold over a type take as arguments?
One argument per constructor; it replaces each constructor with its argument, so one recursive function serves every measure of the type.
From the lesson Folds over trees, options and other structures
-
What did one fold compute for the lesson's tree of five numbers?
Size 5, height 3, sum 21 and the sorted contents [1; 3; 4; 5; 8], each from a one-line use of the same fold.
From the lesson Folds over trees, options and other structures
-
How do pre-, in- and post-order traversals differ when written as folds?
Only in where the node's value goes between the folded subtrees; a tree's map is a fold that rebuilds each node.
From the lesson Folds over trees, options and other structures
-
Why is an in-order iter written with ; rather than built from the fold?
A fold leaves the order of evaluating the two subtrees to the compiler: the fold-based print gave 8 4 1 3 5. iter f l; f v; iter f r fixes the order of the effects.
From the lesson Folds over trees, options and other structures
-
How much stack does a fold over a tree use?
In proportion to the tree's depth: little for a balanced tree, as much as the tree's size for a chain.
From the lesson Folds over trees, options and other structures
-
When do you use Option.map, and when Option.bind?
Option.map when the step always succeeds, Option.bind when the step itself returns an option. Mapping such a step gives an int option option, a sign that bind was meant.
From the lesson Folds over trees, options and other structures
-
What replaces nested matches on options in a chain of steps that may fail?
Option.bind, Option.map, Option.value and Option.fold, or let* defined as Option.bind; the chain stops at the first None.
From the lesson Folds over trees, options and other structures
-
How does structural induction prove a claim for every tree?
Prove it for Leaf, then for Node (l, v, r) assuming it holds for l and r: the same two cases as the function. A test over all small trees finds a false claim's counterexample.
From the lesson Folds over trees, options and other structures
-
What is a sequence, and what does building a pipeline on one compute?
A function that computes its next element when a consumer asks for it. Building the pipeline computes nothing; the consumer computes exactly what it asks for.
From the lesson Sequences, laziness and memoization
-
Searching the numbers below 1,000,000 for the first even square above a million, how many numbers did a list pipeline and a sequence square?
500,000 as a list pipeline and 502 as a sequence, over the numbers below 1,000,000.
From the lesson Sequences, laziness and memoization
-
Why does Seq.length (Seq.ints 0) never return, and what makes an endless sequence safe?
The consumer asks for every element of an endless sequence. Put Seq.take, Seq.take_while or a search such as Seq.find before a consumer that reads to the end.
From the lesson Sequences, laziness and memoization
-
What happens when a plain sequence is read twice, and which functions change that?
Everything is computed twice. Seq.memoize keeps each element after the first read, and Seq.once raises Seq.Forced_twice on a second read.
From the lesson Sequences, laziness and memoization
-
What does Lazy.force do with a suspension?
It runs it at most once and answers every later force from what it kept: the value, or the same exception again. A suspension that forces itself raises Lazy.Undefined.
From the lesson Sequences, laziness and memoization
-
What does OCaml 5.5's Lazy.Mutexed allow that a plain Lazy.t does not?
Forcing one suspension from several domains at once; a plain Lazy.t is not safe to force concurrently.
From the lesson Sequences, laziness and memoization
-
Why does wrapping fib in a memo table from the outside not help its recursive calls?
fib calls itself, not the table, so only the outer answer is stored. Route the recursive calls through the table with a self parameter: fib 30 then ran 31 bodies instead of 2,692,537, which is right only for a pure function.
From the lesson Sequences, laziness and memoization
-
How do you choose between Seq, List and Array for a pipeline?
A list or an array builds every element before anything reads it; a sequence computes only what is read but repeats the work on a second read. Decide by whether the data is read in part, again or by position.
From the lesson Sequences, laziness and memoization
-
What is fixed about an OCaml array, and what does an index outside it do?
Its length, set when it is created. Every a.(i) and a.(i) <- x is bounds-checked, and an index past either end raises Invalid_argument "index out of bounds".
From the lesson Arrays, Dynarray and immutable arrays
-
What does the compiler option -unsafe take away?
The bounds checks on a.(i) and s.[i], and the division-by-zero check. The manual calls such programs slightly faster but unsafe: anything can happen on a bad index.
From the lesson Arrays, Dynarray and immutable arrays
-
Why does Array.make 8 (Array.make 8 '.') put a rook in every row when one cell is set?
The inner array is evaluated once and shared by all eight slots. Build the grid with Array.make_matrix 8 8 '.', or with Array.init 8 (fun _ -> Array.make 8 '.').
From the lesson Arrays, Dynarray and immutable arrays
-
Why are appends to a Dynarray cheap, and what do they cost in memory?
The array behind it grows by a factor rather than by one, so add_last is amortised constant time: 2,000 appends copied 3,576 elements in all. Its memory follows its capacity, not its length.
From the lesson Arrays, Dynarray and immutable arrays
-
When is a plain array the right holder, and when a Dynarray?
An array when the number of elements is settled before building it, as for a grid or a lookup table; a Dynarray when elements arrive one at a time and nobody knows how many.
From the lesson Arrays, Dynarray and immutable arrays
-
What makes an iarray safe to pass around as a shared lookup table?
It cannot be written at all: the module has no set.
From the lesson Arrays, Dynarray and immutable arrays
-
What does one array literal build since OCaml 5.4?
An array, an iarray or a floatarray, depending on the type that is expected where the literal appears.
From the lesson Arrays, Dynarray and immutable arrays
-
How does OCaml store a float array, and what does that save?
Flat: the raw doubles sit in the array itself. A million floats took 1,000,001 words against 5,000,001 as a float option array, and summing them allocated nothing per element.
From the lesson Arrays, Dynarray and immutable arrays
-
What do Map.Make and Set.Make take and give back?
They are functors: from a module with a type t and a compare, they build a module of maps or sets whose bindings and elements always come out sorted by that compare.
From the lesson Maps and sets
-
Why use find_opt instead of find for a key a customer typed?
find raises Not_found for a missing key, often far from the line that caused it; find_opt returns None. Option.value (SMap.find_opt k m) ~default:0 treats a missing key as zero.
From the lesson Maps and sets
-
How do you build a word index in a map?
Fold over the words with update as the step: it reads and writes in one walk, and the function you give it says what a new key and an existing key each become.
From the lesson Maps and sets
-
What happens to a map when you add, update or remove a key?
Nothing: the call returns a new map and the old one stays valid, so earlier versions can be read, compared or kept as undo steps.
From the lesson Maps and sets
-
What does a new version of a map cost?
Only the nodes between the root and the key, about log₂ n of them, are copied, and the rest is shared: a new version of a million-key map cost 120 words.
From the lesson Maps and sets
-
Why can the operator = call two sets with the same elements different, and what answers overlap questions?
The operator = compares tree shapes, which depend on how each set was built; Tags.equal compares the elements. union, inter, diff and subset answer questions about overlap.
From the lesson Maps and sets
-
What must a key's compare satisfy, and how is one written for a record?
A total order: equal keys give 0, swapping the keys flips the sign, and it is transitive. Compare the most important field first and the next only when they are equal; an inconsistent compare accepts every key and then misses some on lookup.
From the lesson Maps and sets
-
Which releases added of_list, to_list and is_singleton for maps?
of_list and to_list arrived in OCaml 5.1 (a repeated key keeps its last binding), and is_singleton, for maps and sets, in 5.5.
From the lesson Maps and sets
-
What does Hashtbl.add do to an earlier binding of the same key?
It hides it: find returns the newest binding, find_all all of them newest first, length counts every one, and remove uncovers the binding below.
From the lesson Hash tables, equality and hashing
-
Which call keeps one binding per key for a running total, and why?
Hashtbl.replace, which overwrites the current binding. add in such a loop leaves every old total hidden underneath and makes length grow without bound.
From the lesson Hash tables, equality and hashing
-
What do OCaml 5.5's Hashtbl.find_and_replace and find_and_remove return?
The previous binding as an option, so None on the first insert of a key.
From the lesson Hash tables, equality and hashing
-
How does the polymorphic Hashtbl test keys, and where can that break?
With the built-in compare and Hashtbl.hash. Comparing two different function-holding keys can raise, a cyclic key may loop, and nan works only because compare treats it as equal to itself.
From the lesson Hash tables, equality and hashing
-
What must equal and hash agree on in Hashtbl.Make?
Hash exactly the parts of a key that equal compares. A hash that forgot to lower-case a city name kept the same city in two buckets.
From the lesson Hash tables, equality and hashing
-
Why did a thousand routes sharing a ten-stop prefix fall into one bucket?
Hashtbl.hash reads at most ten meaningful parts of a key, so keys alike in those parts collide under every seed. Only a hash over the whole key spreads them out.
From the lesson Hash tables, equality and hashing
-
What does a random hash seed protect against, and what does it not?
It stops keys crafted against the fixed seed, but keys with a long shared prefix still collide under every seed.
From the lesson Hash tables, equality and hashing
-
How do several domains share one hash table safely?
Run every access inside a lock such as Mutex.protect m (fun () -> …); four domains then made 100,000 updates with none lost. Unsynchronized access is a programming error.
From the lesson Hash tables, equality and hashing
-
How do a stack and a queue differ in what they give back?
A stack returns the item pushed most recently, last in first out; a queue returns the one that has waited longest, first in first out. Both add and serve in constant time.
From the lesson Stacks, queues, priority queues and buffers
-
How do two stacks model undo and redo, and what must every fresh edit do?
One stack holds the past and the other the undone future. A fresh edit must Stack.clear redo, or redo brings back text from an abandoned branch.
From the lesson Stacks, queues, priority queues and buffers
-
Which jobs suit a stack, and which a queue?
A stack: undo histories, backtracking and depth-first search. A queue: fair scheduling, buffering between producer and consumer, and breadth-first search.
From the lesson Stacks, queues, priority queues and buffers
-
What does OCaml 5.4's Pqueue provide, and at what cost?
MakeMin serves the smallest element and MakeMax the largest; min_elt is constant time and add and pop_min logarithmic, with no promised order among equal priorities.
From the lesson Stacks, queues, priority queues and buffers
-
Why did (Pq.pop_min q, Pq.pop_min q, Pq.length q) return the pops the wrong way round?
OCaml does not fix the order in which a tuple's parts are evaluated. Sequence the pops with a chain of let bindings.
From the lesson Stacks, queues, priority queues and buffers
-
How does Dijkstra's shortest-path method use a min-priority queue?
It keeps (distance, city) pairs compared on distance, always takes the smallest, and skips a stale entry for a city that is already settled.
From the lesson Stacks, queues, priority queues and buffers
-
What did a 10,000-line report cost when joined with ^, against a Buffer?
80,393,105 words with ^ and 488,915 with a Buffer, about 164 times fewer, because each ^ recopies the whole string built so far.
From the lesson Stacks, queues, priority queues and buffers
-
May two domains use one Stack or Queue without a lock?
No: the manual calls unsynchronized access a programming error. Guard a shared one with a Mutex, or give each domain its own.
From the lesson Stacks, queues, priority queues and buffers
-
Which module does the file my_shapes.ml define, and why does my-shapes.ml fail?
My_shapes: every .ml file is a module named after the file with its first letter capitalised. my-shapes.ml compiles with warning 24, but no other file can name the module.
From the lesson Modules, files and namespaces
-
Why can shape.ml stop with Error: Unbound module Vector?
A file compiles only once the modules it names have compiled interfaces, so compiling shape.ml before vector.ml fails.
From the lesson Modules, files and namespaces
-
What are a dune library's modules called inside the library and outside it?
Outside, Geometry.Vector and Geometry.Shape; inside, plain Vector and Shape, because dune opens the library module it generates. (wrapped false) puts them at the top level.
From the lesson Modules, files and namespaces
-
How is a module defined inside a file reached, and how do you shorten its name?
By a path such as Units.Distance.of_km. module D = Units.Distance gives a shorter name that the compiler treats as the same module.
From the lesson Modules, files and namespaces
-
How far do open M, let open M in e and M.( e ) reach?
open M to the end of the file; the two local forms stop at the end of one expression. Since OCaml 5.2, M.( … ) also works inside types.
From the lesson Modules, files and namespaces
-
How do include and open differ?
include copies a module's definitions into the module being written, so they are exported: Extend.List exports all of List plus sum. open only shortens names and exports nothing.
From the lesson Modules, files and namespaces
-
What can a top-level open SMap silently do to a later compare 3 4?
Make it mean SMap.compare. Warnings 44 and 45 report such hiding once switched on, open! marks a hiding you intend, and a qualified name such as Stdlib.compare avoids the question.
From the lesson Modules, files and namespaces
-
When is a qualified name, a local open or a top-level open the better choice?
A qualified name when a module is used a few times, a local open when one expression uses it a lot, and a top-level open only for modules made to be opened, such as Printf.
From the lesson Modules, files and namespaces
-
What does a signature list, and how does an .mli file relate to it?
The names and types a module offers. An .mli file is that signature next to the .ml, and dune checks the implementation against it on every build.
From the lesson Signatures, abstract types and .mli files
-
What does type t with no definition in a signature do?
It makes t abstract: outside the module, values of the type can be built and read only through the functions the signature lists.
From the lesson Signatures, abstract types and .mli files
-
How does the toplevel print a value of an abstract type?
As <abstr>, because outside the module the value no longer has the representation's type.
From the lesson Signatures, abstract types and .mli files
-
What stops compiling once a Counter whose type was plainly int is sealed with an abstract type?
Counter.incr 5, adding two counters together and using -1 as a counter: every counter must now come from the module's own functions, such as create.
From the lesson Signatures, abstract types and .mli files
-
What happens to a definition that a module's .mli leaves out?
It is private to the module, which keeps the public surface small while the implementation stays as large as it needs.
From the lesson Signatures, abstract types and .mli files
-
What is a functor?
A function from modules to modules. Its body may use only what its parameter's signature declares, and the compiler checks an application such as Map.Make (String) against that signature.
From the lesson Functors and module constraints
-
When does the body of a functor run?
Once per application, when the program runs. The compiler only checks the argument against the parameter's signature and works out the result's types.
From the lesson Functors and module constraints
-
What do Map.Make, Set.Make and Hashtbl.Make ask of their argument module?
Map.Make and Set.Make need type t and compare; Hashtbl.Make needs equal and hash. An argument missing one is refused with the name of the missing item.
From the lesson Functors and module constraints
-
What does the one line Map.Make (String) give back?
A module of 44 values, such as add, find, bindings and fold, all working on string keys.
From the lesson Functors and module constraints
-
How can one functor's result be another functor's argument?
A functor returns a module, so applications nest: ListShow (OptionShow (IntShow)) printed a list of optional numbers.
From the lesson Functors and module constraints
-
What does a sharing constraint such as with type t = E.t list do to a functor's result?
It keeps a type the argument supplied visible to callers; with a bare result signature the type would be abstract.
From the lesson Functors and module constraints
-
How do you turn a module into a value, and what type does the value have?
(module M : S) packs a module that matches S into a value of the package type (module S), which lists, tables and functions handle like any other value.
-
Which forms turn a packed value back into a module?
A let (module F : S) = v in … pattern, a function parameter written as that pattern, or module F = (val v : S), the form used at the top level.
-
Why use a table of packed modules instead of a functor application?
A functor application is written into the source, so its choice is fixed at compile time. A table of packed modules lets one compiled program pick an implementation from its input, such as a format named on the command line.
-
What does the OCaml 5.5 type (module M : MONOID) -> M.t list -> M.t say?
The function takes a module and names it in the rest of its type, so the result's type depends on the module written at the call.
-
What does "make illegal states unrepresentable" mean?
Choose types so a value that breaks the rules cannot be built: an order as a variant whose Draft holds nothing, Paid a payment, and Shipped a payment and a tracking id.
From the lesson Type-driven design, invariants and phantom types
-
What is wrong with modelling an order as a record of boolean flags and optional fields?
It type-checks combinations that never happen, such as shipped = true with payment = None, so every function that reads an order must handle cases the rules forbid.
From the lesson Type-driven design, invariants and phantom types
-
Where does a constructor per state move the cost of impossible combinations?
From every reader to the one place a value is built, where the compiler enforces it.
From the lesson Type-driven design, invariants and phantom types
-
How should untrusted input be validated?
Once, at the boundary: allowlist what is acceptable, check the type and the range, return a result, and keep the valid value in a type that only that path builds.
From the lesson Type-driven design, invariants and phantom types
-
Why validate an amount at the server when the web form already checked it?
Client-side checks can be bypassed, and data from internal feeds and queues needs the same check: what matters is crossing a trust boundary, not the transport.
From the lesson Type-driven design, invariants and phantom types
-
What does an exception do to a running OCaml program?
It breaks normal flow, names a failure and carries its details; a handler decides what happens next.
From the lesson Exceptions and resource cleanup
-
How does your own exception carry several named details?
With an inline record in its definition, such as exception Insufficient_funds of { needed : int; available : int }, so the handler can name the fields directly. Inline records arrived in OCaml 4.03.
From the lesson Exceptions and resource cleanup
-
Why does a reader that opens a channel, calls input_line and then close_in leak on an empty file?
input_line raises End_of_file before close_in runs, so the channel stays open; the descriptor is not released until the process ends.
From the lesson Exceptions and resource cleanup
-
Where should a resource that must be released live?
Inside In_channel.with_open_text, with_open_bin or Fun.protect, never between an open and a close that an exception can skip.
From the lesson Exceptions and resource cleanup
-
Why is Fun.protect ~finally (work ()) rejected?
Fun.protect expects a value of type unit -> 'a, not the result of the work. Write Fun.protect ~finally work or Fun.protect ~finally (fun () -> work ()).
From the lesson Exceptions and resource cleanup
-
How do you get a backtrace that names the line that raised?
Compile with -g and run with OCAMLRUNPARAM=b, or call Printexc.record_backtrace true at start-up and log Printexc.get_backtrace () in the top-level handler.
From the lesson Exceptions and resource cleanup
-
When is match … with exception the better form, and when try … with?
match … with exception when you handle both a result and an exception of the same call; try … with when the work spans several statements and an exception from any of them should be caught.
From the lesson Exceptions and resource cleanup
-
When should code use List.assoc_opt rather than List.assoc?
When a missing key is an expected outcome: assoc_opt returns None instead of raising Not_found. Keep the raising form for a lookup that should never fail.
From the lesson Exceptions and resource cleanup
-
How do result and option differ as ways of returning a failure?
result carries the reason a step failed, Error e, as a value the type checker will not let the caller drop; option reports a failure that has nothing to say about itself, None.
From the lesson Result, option and binding operators
-
What happens in a chain of result steps when one of them fails?
The first Error reaches the caller untouched, and every later step is skipped.
From the lesson Result, option and binding operators
-
What is let* in Result.Syntax, and what replaces it on older compilers?
Result.bind written as a binding operator, in Result.Syntax since OCaml 5.4. On older compilers, let ( let* ) = Result.bind is the one-line fallback.
From the lesson Result, option and binding operators
-
Why avoid Result.get_ok on a value that may be an Error?
It raises Invalid_argument "result is Error _". Match on the result, or use Result.value ~default when a default makes sense.
From the lesson Result, option and binding operators
-
What does Result.map do to an Ok and to an Error?
It applies the function inside Ok and leaves an Error exactly as it was.
From the lesson Result, option and binding operators
-
Why return a form's validation failures as values rather than raise them?
Each failure comes back as a value the type checker refuses to let the caller forget, while an exception that nobody handles stops the program.
From the lesson Result, option and binding operators
-
How does an OCaml program read and write files?
Through channels, abstract handles from the In_channel and Out_channel modules, which can read from or write to a file, a pipe or the standard streams.
From the lesson Files, channels and processes
-
What does In_channel.with_open_text do for you?
It opens the file, passes the channel to your function and closes the channel afterwards, even if the function raises.
From the lesson Files, channels and processes
-
When do you read with input_all or input_lines, and when with fold_lines?
The first two for small files; fold_lines for files large enough that holding every line would cost memory or time, because it processes one line at a time in constant memory.
From the lesson Files, channels and processes
-
What did fold_lines count in the six-line log, and what did it never do?
(3, 1, 2): three INFO, one WARN and two ERROR lines. It never held the whole file in memory.
From the lesson Files, channels and processes
-
What happens when a program opens a file that does not exist?
It raises Sys_error carrying the path and the reason; catching it lets the program report the problem instead of crashing.
From the lesson Files, channels and processes
-
How does a command line reach an OCaml program?
As Sys.argv, an array of plain strings: index 0 is the program's own name, and the later entries mean nothing until your code gives them a meaning.
From the lesson Command-line tools with Arg and Cmdliner
-
What does Arg.parse specs anon_fun usage_msg take?
A spec list of keyword, spec and doc string, such as an Arg.Set_int that fills a ref, a function for anonymous arguments, and a usage message. It answers -help and --help itself.
From the lesson Command-line tools with Arg and Cmdliner
-
What does Arg.parse do when it meets an option missing from its spec list?
It stops the program with status 2 after printing, to standard error, the reason (such as unknown option '--top'), the usage message and the list of options.
From the lesson Command-line tools with Arg and Cmdliner
-
Why does a spec entry ("-top", …) refuse --top 2?
An Arg keyword is matched exactly as written. List both spellings, or use a library where --top and -n can name one option.
From the lesson Command-line tools with Arg and Cmdliner
-
What does one Cmdliner declaration of an option carry?
Every name of the option, its type, its default and its documentation; Cmdliner builds the parser, the man page and the documented exit statuses from such declarations.
From the lesson Command-line tools with Arg and Cmdliner
-
What do -, 0 and .2 do inside a Printf conversion such as %-20s or %8.2f?
The width is a minimum: the - flag pads on the right, the 0 flag pads with zeros, and a precision such as .2 fixes the digits after the point.
From the lesson Printing with Printf and Format
-
What did the format "%-20s %3d %8.2f\n" do to every item row of the bill?
It printed each row at 33 characters, the name on the left in 20 and the numbers on the right.
From the lesson Printing with Printf and Format
-
How does the compiler check a Printf.printf call?
It types the call by the conversions of its format literal: a wrong argument is a compile error, and a missing one leaves a function waiting for it.
From the lesson Printing with Printf and Format
-
Why can a format read from a file, or joined with ^, not be passed to Printf.printf?
The result has type string, which the compiler refuses. Keep the format a literal, annotated with _ format when it is named, and pass the text that changes as %s arguments.
From the lesson Printing with Printf and Format
-
What do %a and %t hand over to your own code?
%a takes a printer and a value and lets the printer write that part of the line; %t takes a printer that needs only the channel.
From the lesson Printing with Printf and Format
-
What does %! do, and when does it matter?
It flushes the channel, which keeps lines in order when stdout and stderr share one file.
From the lesson Printing with Printf and Format
-
How did one Format printer give one, two or three lines?
Through boxes and break hints: a hint breaks only when the rest of its box does not fit, so margins 78, 40 and 32 gave one, two and three lines.
From the lesson Printing with Printf and Format
-
What do Format.asprintf and OCaml 5.5's l*printf functions offer?
asprintf returns a printer's text as a string, at margin 78; the l*printf forms take the arguments as one typed Args list and report a missing or extra argument at the list.
From the lesson Printing with Printf and Format
-
What is a tag such as `Red or `Blue 3?
A polymorphic variant: a value written with a backquote, with no type declared first. OCaml infers a row type for each use.
From the lesson Polymorphic variants
-
What does a match without a wildcard do to its argument's type?
It gives it a closed row, [< …]: at most the listed tags, so a call with any other tag is refused before the program runs.
From the lesson Polymorphic variants
-
Does is_red : [> `Red ] -> bool accept only `Red?
No: the sign > means at least, so is_red `Blue type-checks and returns false. A row that lists everything a function accepts is written with <.
From the lesson Polymorphic variants
-
What is a polymorphic variant type with neither < nor >?
Exact: it lists exactly the tags. Both signs hide a row variable, so such a type can still change as more uses are checked.
From the lesson Polymorphic variants
-
When does a call with a polymorphic variant argument type-check?
Only when every tag the value's type includes is one the function's row allows.
From the lesson Polymorphic variants
-
What is an immediate object?
An object … end expression with no class: a few mutable val fields grouped with the methods allowed to touch them.
-
What type did the metro card object get, and what does the type leave out?
< balance : int; tap : int -> unit >, a listing of its methods. The val fields stay private, even one that shares a method's name.
-
A card starts at 200 and takes card#tap 30 three times. What is left, and what happens to card#refund 50?
110, because each call changes the one field inside the object. card#refund 50 is refused because the card's type has no such method.
-
When do two objects have the same type?
Exactly when they have the same methods with the same types, however each was built.
-
What type does let get_x o = o#x get, and what does .. stand for?
< x : 'a; .. > -> 'a: any object with at least an x method. The .. is a row variable that stands for every other method the object may have.
-
Why does get_x need no cast, while a list of different objects needs :>?
The row variable makes get_x one polymorphic function, not a parent type. The coercion :> is needed only where objects of different types must share one type, such as list elements.
-
What is a dune project made of, and what separates a public library from a private one?
A graph of libraries, executables and tests. A public library has a (public_name …); a private library has none.
From the lesson dune in depth: libraries, profiles and stanzas
-
Why does dune refuse a public library that lists a private library in (libraries …)?
A public library may not depend on a private one. Give the private library a public name, keep the dependency in the executable and the test, or install it under the package with (package …).
From the lesson dune in depth: libraries, profiles and stanzas
-
What do the dev and release profiles do with the compiler's default warnings?
Dev passes -short-paths -keep-locs -warn-error +a, so every default warning is an error; release has empty flags, so the same warnings are only printed.
From the lesson dune in depth: libraries, profiles and stanzas
-
How do you silence one warning that is wrong for a project without leaving the dev profile?
With (env (dev (flags (:standard -w …)))), which changes the dev flags only, so the strict default still catches every warning you have not chosen to silence.
From the lesson dune in depth: libraries, profiles and stanzas
-
Which profile does dune build -p ledger_ext, the command opam uses to install, build with?
Release: -p chooses it for you.
From the lesson dune in depth: libraries, profiles and stanzas
-
What does a (rule) stanza do?
It generates a file during the build from (target …), (deps …) and (action …); the example wrote version.ml from a one-line VERSION file and reran when VERSION changed.
From the lesson dune in depth: libraries, profiles and stanzas
-
What do (alias …) and (preprocess (pps …)) add to a dune file?
An alias names a group of targets that you build with @name; (preprocess (pps …)) wires PPX rewriters such as ppx_expect into a library's source.
From the lesson dune in depth: libraries, profiles and stanzas
-
Which dune commands list what a folder holds, and which cover the rest of the daily workflow?
dune describe aliases, dune describe targets and dune describe tests list it; dune utop, dune fmt, dune build --watch and dune build @doc cover the rest.
From the lesson dune in depth: libraries, profiles and stanzas
-
What does Alcotest.(check int) take, in order?
A testable such as int, which says how to compare and print values; a message; the expected value; and the value the code computed.
From the lesson Unit tests and expect tests
-
What goes wrong when the computed value is passed before the expected one?
The case still fails, but the report swaps expected and received, which sends the reader after the wrong number.
From the lesson Unit tests and expect tests
-
How does Alcotest report a failed case, and what happens to dune test?
It names the case by group, number and name, with the message and both values; the suite exits with status 1, so dune test fails too.
From the lesson Unit tests and expect tests
-
What does splitting 1,000 three ways with integer division silently lose?
One unit: each share is 333 and the shares add up to 999, and nothing is reported. A unit test that names the expected total makes it fail.
From the lesson Unit tests and expect tests
-
Which of a project's tests does one dune test run?
All of them, unit tests, expect tests and cram tests alike, and it fails when any test fails.
From the lesson Unit tests and expect tests
-
How does a property-based test differ from a unit test?
It states one claim and lets the library draw the cases: one named test case stood for the 500 generated cases that ~count:500 checked.
From the lesson Property-based testing and fuzzing
-
What does QCheck.Test.make take, and how does the property reach dune test?
A generator, a count and a name, besides the property; QCheck_alcotest.to_alcotest puts it into the Alcotest list that dune test runs.
From the lesson Property-based testing and fuzzing
-
Which QCheck 0.90 renames break older examples?
small_list became list_small, and small_int and small_nat became nat_small. The dev profile turns the deprecation alert into an error.
From the lesson Property-based testing and fuzzing
-
Should a deprecation alert be silenced with -alert -deprecated to make the build pass?
No: the alert is a rename notice, not a fault in the property. Read the line that names the replacement and use it.
From the lesson Property-based testing and fuzzing
-
When does coverage-guided fuzzing earn its setup, and when is a property the better tool?
Fuzz when the input is untrusted bytes and any crash is a bug, accepting an instrumented compiler; write a property when you can say what the right answer looks like.
From the lesson Property-based testing and fuzzing
-
How do attributes and extension nodes differ?
Attributes, written [@…], [@@…] or [@@@…], attach notes to code; extension nodes, [%…], are placeholders that a PPX must replace before type checking.
From the lesson PPX, attributes and deriving
-
Which attributes does the compiler read itself, and what does it do with the others?
A few, such as [@tailcall] and [@warning "-26"], which change how it checks or compiles the code in every build, with no PPX. Attributes it does not know are ignored.
From the lesson PPX, attributes and deriving
-
When does a PPX rewriter run on your code?
After parsing and before type checking, and only when the library's dune file asks for it under (preprocess (pps …)).
From the lesson PPX, attributes and deriving
-
What does [@@deriving show, eq, ord] produce?
Through ppx_deriving, show_* printers, equal_* and compare_* functions for the type: eight functions for two types in a 12-line file, none written by hand.
From the lesson PPX, attributes and deriving
-
What happens when a library uses [@@deriving show] but its dune file has no pps field?
The library still builds, and the missing functions show up as an unbound value where they are used.
From the lesson PPX, attributes and deriving
-
Who reads [@@deriving show], and who reads [@tailcall]?
ppx_deriving reads the first before type checking and adds code; the compiler reads [@tailcall], generates nothing and warns with warning 51 when the call is not in tail position.
From the lesson PPX, attributes and deriving
-
What does Yojson.Safe.from_string do with JSON text?
It turns the text into a tree of polymorphic variants such as `Assoc and `Int, and raises Yojson.Json_error with a position on a syntax error.
From the lesson JSON and safe serialisation
-
What does a JSON parser check, and what is left to your code?
Only that the text is JSON. Whether the members you need are present, with the expected constructors and sensible values, is decided by your own match on the tree.
From the lesson JSON and safe serialisation
-
What should happen to an order whose amount arrives as "1250.00"?
Your match on the tree refuses it rather than guessing, because a string is not the integer amount the order needs.
From the lesson JSON and safe serialisation
-
How do JSON, S-expressions and Marshal differ in what a reader checks?
JSON and S-expressions are text that a reader checks field by field; Marshal stores no type and checks nothing.
From the lesson JSON and safe serialisation
-
How does the source of the bytes decide the format?
Untrusted bytes, from users, partners or the network, call for JSON (or S-expressions in OCaml tools) read by a decoder that checks them. Bytes your program wrote for itself may use Marshal, behind a check that refuses any other file.
From the lesson JSON and safe serialisation
-
What does (generate_opam_files true) in dune-project do, and why not edit the result?
dune writes the opam file from the (package …) stanza. Its first line says to edit dune-project instead, because the next build overwrites the generated file.
From the lesson opam packages, documentation and CI
-
How does another machine install a project's dependencies the same way?
In a project-local switch, opam install . --deps-only installs what the opam file names, so nothing depends on what one developer happens to have installed.
From the lesson opam packages, documentation and CI
-
A teammate's build of the same commit fails with Library "alcotest" not found. What was missing?
A package description naming the dependency: once the opam file lists it, opam installs it from the file into the teammate's switch.
From the lesson opam packages, documentation and CI
-
Where does dune build @doc write the documentation, and from what?
Under _build/default/_doc/_html/, rendered offline from every (** … *) comment.
From the lesson opam packages, documentation and CI
-
What makes up a shipping OCaml library?
A package you install, a switch that isolates it, documentation rendered from the source, and a CI job that runs the same build on every push.
From the lesson opam packages, documentation and CI
-
What is a GADT, and how does it differ from a plain variant?
A variant whose constructors each name their own return type after a colon, such as Int : int -> int expr and Bool : bool -> bool expr. A plain variant puts every constructor in one type.
From the lesson Generalised algebraic data types (GADTs)
-
What goes wrong when a tiny language's evaluator is built on an ordinary variant?
Add has to assert that both sub-terms are integers and If that its condition is a boolean, so a bad input surfaces at run time as Failure "int in Add".
From the lesson Generalised algebraic data types (GADTs)
-
What does Add : int expr * int expr -> int expr do to the term Add (Int 1, Bool true)?
It makes the term a compile-time error instead of a run-time failure, and a match on an int expr needs only the arms that return an int expr.
From the lesson Generalised algebraic data types (GADTs)
-
Why does a function over a GADT need type a. in let f : type a. a expr -> a = function …?
The locally abstract type lets each arm refine a to the type its own constructor built.
From the lesson Generalised algebraic data types (GADTs)
-
What does type showable = Show : 'a * ('a -> string) -> showable hide, and what may code do with it?
It hides the type of the value, packed together with its printer; the only safe move is to call that printer on the value.
From the lesson Generalised algebraic data types (GADTs)
-
Where does the OCaml 5.5 standard library itself use GADTs?
Printf.lprintf reads a heterogeneous list driven by its format, and type _ Effect.t += Ask : string -> int Effect.t extends an open GADT.
From the lesson Generalised algebraic data types (GADTs)
-
Why does let r = ref [] print as '_weak1 list ref?
ref [] is a function call, not a syntactic value, and its variable sits inside a reference that can be written. The first store, such as r := [1], fixes it to int list ref for good.
-
What could go wrong if ref [] stayed polymorphic?
r := [1] followed by reading r as a string list would type-check and crash at run time.
-
How do you remove a weak type variable?
Annotate the value, as in let r : int list ref = ref [], eta-expand the function, or create the reference inside the function that uses it.
-
Why does List.map Fun.id [] print as 'a list, although it is a call?
The relaxed value restriction still generalises a type variable that appears only covariantly.
-
What do +'a and -'a promise about a type parameter?
+'a says the type only hands values of 'a out; -'a says it only takes them in. A parameter used both ways, as in a mutable cell, is invariant.
-
What happens when a signature claims a variance the implementation does not keep?
The build fails with "Their variances do not agree".
-
What limit do a plain parameter and a recursive function's own calls share?
Each has a single type inside the function. Polymorphic recursion with an explicit 'a. lifts the limit for a recursive function's own calls, and an OCaml 5.5 polymorphic parameter lifts it for a parameter.
-
What is an effect in OCaml 5?
A question that deep code asks with perform and the code around the call answers in a handler, so a setting reaches a function four calls down without a parameter or a global.
From the lesson Effect handlers
-
How is an effect declared, and why does perform (Ask "width") have type int?
As a constructor of the extensible GADT Effect.t, type _ Effect.t += Ask : string -> int Effect.t, whose index is the answer's type.
From the lesson Effect handlers
-
In match … with effect (Ask name), k -> continue k v, what are k and continue?
k is the suspended rest of the computation; continue k v makes the perform return v, runs the rest under the same deep handler and returns what the rest produced.
From the lesson Effect handlers
-
What happens when no handler covers a performed effect?
Effect.Unhandled is raised at the perform, at run time only, because OCaml does not check effects at compile time. A handler with no case for an effect passes it to the next handler out.
From the lesson Effect handlers
-
How many times may a continuation be resumed, and what does discontinue do?
Exactly once: a second continue raises Continuation_already_resumed, and a dropped one never runs its cleanup. discontinue ends it with an exception so that Fun.protect can tidy up.
From the lesson Effect handlers
-
Why was a handler refused with "This instance of int is ambiguous"?
The answer's type inside the effect case came from outside it. Annotate where the value comes from, as in (settings : (string * int) list).
From the lesson Effect handlers
-
What changed for handlers in OCaml 5.3, and how did code look before?
effect became a keyword, so match … with effect works; before, a handler was a record of retc, exnc and effc passed to Effect.Deep.match_with, which still works. Code that names a value effect needs -keywords 5.2.
From the lesson Effect handlers
-
How does an effect handler differ from an exception handler?
Both are the nearest enclosing handler with a matching case, but an effect handler also gets the continuation and can resume the code from the perform, while the code that raised an exception is already abandoned.
From the lesson Effect handlers
-
Why can a caller not pause List.iter between two elements?
A push-style function keeps its loop to itself: it calls the given function once per element and returns only when the input is exhausted.
From the lesson Generators and schedulers with effects
-
How does a Yield effect turn a push-style iterator into a generator?
Each perform suspends the loop with its remaining steps, the handler acts as the missing consumer, and continue lets the loop take one more step.
From the lesson Generators and schedulers with effects
-
What did to_list return for List.iter over [3; 1; 4], and for a range written as a for loop?
[3; 1; 4], and [10; 11; 12; 13; 14] for the range: the same handler works on any function of type (int -> unit) -> unit.
From the lesson Generators and schedulers with effects
-
What does a cooperative round-robin scheduler built on effects use?
Fork and Yield effects, which let three tasks share one thread in a deterministic order; promises with await can run on top of it.
From the lesson Generators and schedulers with effects
-
How long do two slow waits take one after the other, and together?
One after the other, the sum of the two; together, the longer of the two.
From the lesson Concurrent I/O with Eio and Lwt
-
What do Eio.Fiber.both and Fiber.pair do?
They run two fibers and return when both have finished, so the wall time is the slower wait, not the sum; if one raises, the other is cancelled.
From the lesson Concurrent I/O with Eio and Lwt
-
What does Eio.Switch.run guarantee about the fibers forked onto its switch?
It returns only when every fiber forked with Fiber.fork ~sw has finished, releases what is attached to the switch, and cancels the other fibers when one of them raises.
From the lesson Concurrent I/O with Eio and Lwt
-
In direct style, what does a function that waits return, compared with the Lwt version?
Its answer's own type, string for Buf_read.line, where the Lwt version returns string Lwt.t.
From the lesson Concurrent I/O with Eio and Lwt
-
How do Eio fibers differ from two systhreads of one domain?
Fibers are switched by Eio, inside one thread, at the points where they wait; each systhread is an operating-system thread with its own stack.
From the lesson Concurrent I/O with Eio and Lwt
-
What do Domain.spawn and Domain.join do?
Domain.spawn starts a function on a new domain, which runs OCaml code on another processor; Domain.join waits for its result.
From the lesson Parallelism with domains and atomics
-
What does Domain.recommended_domain_count () tell you, and how many domains should a program spawn?
The runtime's recommended ceiling for parallel work, 18 on the lesson's machine. Spawn about one domain per core and feed work into them, because each spawn allocates a stack and a minor heap.
From the lesson Parallelism with domains and atomics
-
Four domains each added 1 to a shared ref a million times. What came out, and why?
1,921,322, 1,777,458 and 2,140,435 instead of 4,000,000: r := !r + 1 reads, adds and writes in three steps, so another domain can slip in between and overwrite an increment.
From the lesson Parallelism with domains and atomics
-
Which fixes make the shared counter correct?
Atomic.incr, Mutex.protect, or an OCaml 5.4 [@atomic] record field; a mutex is the only one of the three when the indivisible step covers more than one field.
From the lesson Parallelism with domains and atomics
-
What does a Domainslib task pool do, and where do await and the parallel loops run?
It keeps a fixed set of domains busy; await and the parallel loops run inside T.run, the handler of their effect.
From the lesson Parallel patterns and race detection
-
How should a parallel speed-up be measured?
Run both versions once to check they agree, then time several wall-clock runs of each and compare the medians; the prime count gave 7.2, 12.5 and 6.6 times on 18 domains.
From the lesson Parallel patterns and race detection
-
Why must the reducer of parallel_for_reduce be associative?
The range is split between domains and the parts are combined by the reducer: ( + ) summed 1 to 1,000,000 to 500000500000 on 18 domains as on one, while ( - ) would give an answer that depends on the split.
From the lesson Parallel patterns and race detection
-
How does a parallel loop's body avoid shared state?
It returns its own part and leaves combining the parts to the reducer.
From the lesson Parallel patterns and race detection
-
Which standard structures need a mutex between domains, and what else is named for sharing and races?
Queue, Stack, Hashtbl and Buffer need a mutex; Saturn's structures are built to be shared, and ThreadSanitizer is OCaml's tool for detecting races.
From the lesson Parallel patterns and race detection
-
How is every OCaml value stored?
In one word. Ints, chars, bools, unit, [] and constructors without arguments are immediate integers, marked by the lowest bit; every other value is the address of a block in the heap.
From the lesson Memory representation and the garbage collector
-
What does a block's header hold, and how are constructors numbered?
Its size and its tag. Constructors with arguments are numbered from 0 in the order they are declared, while strings, floats and closures have tags of their own.
From the lesson Memory representation and the garbage collector
-
How many words do Some 42, a list cell, a pair of floats and a record of two floats take on a 64-bit machine?
2, 3, 7 and 3. Each boxed float is a 2-word block of its own, while a record of floats is stored flat with tag 254.
From the lesson Memory representation and the garbage collector
-
Why is (Obj.magic seat : int) a bad way to store a constructor's status code?
The numbers follow declaration order, so one constructor added above shifts every stored code with nothing for the compiler to report. Map each constructor to its code with a match.
From the lesson Memory representation and the garbage collector
-
Why is data that dies young cheap for the OCaml collector?
New blocks go to the minor heap, and a minor collection copies only the survivors to the major heap and reuses the rest at once: 13.8 million words of short-lived data asked less of it than the 1.4 million words kept.
From the lesson Memory representation and the garbage collector
-
Why use Gc.quick_stat rather than Gc.stat for counters read often?
Gc.stat runs a full major collection before it answers; Gc.quick_stat returns the counters as they stood at the last minor collection or major cycle.
From the lesson Memory representation and the garbage collector
-
Why did OCAMLRUNPARAM=a=2 print exactly the default numbers on OCaml 5?
OCaml 5 has no a letter and skips letters it does not know, and Gc.set ignores allocation_policy, window_size and max_overhead. Change s or o instead.
From the lesson Memory representation and the garbage collector
-
What do Gc.ramp_up (OCaml 5.4) and Gc.Tweak (5.5) do?
Gc.ramp_up suspends the major collector's extra work while live memory grows; Gc.Tweak reads and sets named parameters meant mainly for the collector's developers.
From the lesson Memory representation and the garbage collector
-
What does #trace f print in the toplevel?
For every later call, the argument (f <-- 16) and the result (f --> 4), and any exception that escapes f. It works only in the toplevel.
From the lesson Debugging, profiling and benchmarking
-
How do you find a bug with a trace?
Trace the smallest failing input: the first line that disagrees with your own calculation marks the faulty step, often the base case.
From the lesson Debugging, profiling and benchmarking
-
What does an uncaught exception show by default, and which backtrace frame do you read first?
Only its name, such as Fatal error: exception Not_found. With OCAMLRUNPARAM=b a backtrace appears; read its first frame in your own code, not the library frame at the top.
From the lesson Debugging, profiling and benchmarking
-
Which tool fits which symptom?
A trace or a debugger for wrong values, a backtrace for exceptions, a profiler for time and an allocation profiler for memory.
From the lesson Debugging, profiling and benchmarking
-
Why does a performance fix need both a profile and a benchmark?
A profile says where the time goes; a benchmark says how much a change saved.
From the lesson Debugging, profiling and benchmarking
-
How does OCaml call a routine that is already written in C?
Through its foreign function interface: declare the function with external, write a small C stub that connects OCaml's calling conventions to C's, and let the build compile and link it.
From the lesson Interfacing with C
-
What does the compiler check about an external declaration's type?
Nothing: it trusts the type you give and calls the C function directly, so a wrong type is a memory bug, not a type error.
From the lesson Interfacing with C
-
How does a C stub read its arguments and return an integer?
Its arguments are OCaml values read through macros such as String_val and Long_val, and it returns an immediate with Val_long.
From the lesson Interfacing with C
-
Why must a stub not read an OCaml string with strlen, or keep its char *?
An OCaml string may contain null bytes, so strlen can give the wrong length, and the collector may move the block, so a saved pointer can dangle. Use String_val and caml_string_length each time.
From the lesson Interfacing with C
-
What actually runs when an OCaml program runs in a browser?
A translation of its bytecode: JavaScript from js_of_ocaml or WebAssembly from wasm_of_ocaml. OCaml itself does not run there.
From the lesson OCaml in the browser: js_of_ocaml and Wasm
-
What does (modes byte exe js wasm) do?
It turns one source file into four programs that one dune build emits side by side; it is a build-time switch, not a run-time choice, and the source does not change.
From the lesson OCaml in the browser: js_of_ocaml and Wasm
-
What is Sys.int_size on each back end?
63 natively and as bytecode, 32 under js_of_ocaml and 31 under wasm_of_ocaml.
From the lesson OCaml in the browser: js_of_ocaml and Wasm
-
How should a native 63-bit identifier travel to a browser in JSON?
As a string, or in a type wider than the browser back end's int, because a native value may not fit there.
From the lesson OCaml in the browser: js_of_ocaml and Wasm
-
When is (modes js) enough?
For a front end that only ever runs in the browser.
From the lesson OCaml in the browser: js_of_ocaml and Wasm
-
What does a lexer decide, and what does a parser decide?
The lexer turns characters into tokens, one per call, such as INT 12 or PLUS, dropping blanks and comments; the parser groups the tokens by a grammar and runs a semantic action each time it reduces a rule.
From the lesson Lexing and parsing with ocamllex and Menhir
-
Which rule does ocamllex use when several patterns match?
The one that matches the most characters, and on a tie the rule written first.
From the lesson Lexing and parsing with ocamllex and Menhir
-
Why does the word let come out as IDENT "let"?
The rule for names comes first and both patterns match three characters, so the earlier rule wins. Put keyword rules first, or look each name up in a table of keywords.
From the lesson Lexing and parsing with ocamllex and Menhir
-
What does a catch-all _ rule at the end of a lexer give you?
Any stray byte becomes an error that carries its offset.
From the lesson Lexing and parsing with ocamllex and Menhir
-
What must dune-project hold before dune 3.24.2 runs Menhir?
The line (using menhir 3.0): dune 3.24.2 stopped with an error instead of adding it by itself.
From the lesson Lexing and parsing with ocamllex and Menhir
-
What do precedence lines do in a Menhir grammar?
They rank the operators from loosest to tightest and give each rank an associativity, so 8 - 3 - 2 gives 3 rather than 7. Without them Menhir still builds a parser, which grouped every operator to the right.
From the lesson Lexing and parsing with ocamllex and Menhir
-
Where does the offset of a syntax error come from?
From the lexer buffer, because Parser.Error carries nothing.
From the lesson Lexing and parsing with ocamllex and Menhir
-
Why is Menhir usually chosen over ocamlyacc?
Menhir accepts LR(1) grammars, lets rules name their parts, explains conflicts in terms of the grammar and builds reentrant parsers; ocamlyacc accepts only LALR(1), refers to values as $1 and $3, and its parsers share state.
From the lesson Lexing and parsing with ocamllex and Menhir
-
What is a program to an interpreter written in OCaml?
A syntax tree: a variant type with one constructor per kind of expression, so every function over it has one case per constructor.
From the lesson Writing an interpreter and a type checker
-
What does an evaluator take, and how does it handle names?
An environment and a tree: each case reads names from the map it was given and passes a new map to its children.
From the lesson Writing an interpreter and a type checker
-
Why does a persistent map make a good environment for an evaluator?
A let then needs no undoing when its body ends: the body gets a new map, and the outer one is unchanged.
From the lesson Writing an interpreter and a type checker
-
Why does let n = 10 in let add = fun x -> x + n in let n = 99 in add 1 give 11?
add is a closure, its code paired with the environment in force when it was made, where n is 10; the later rebinding of n does not change what it sees.
From the lesson Writing an interpreter and a type checker
-
How does Dream route a request and read a path parameter?
Dream.router with Dream.get, Dream.post and :name segments sends each request to one handler; Dream.param request "name" reads the segment with percent-encoding already decoded.
From the lesson Web services with Dream
-
What does Dream.html_escape cover, and where is it not enough?
It turns the characters <, >, &, the double quote and the single quote into entities, for element content and quoted attributes. Unquoted attributes, inline scripts and inline CSS need other encodings.
From the lesson Web services with Dream
-
Which pieces make up Dream's built-in CSRF defence?
Dream.set_secret, a session middleware, Dream.csrf_tag in the form, and Dream.form, which checks the token while it parses the body.
From the lesson Web services with Dream
-
Why does Dream.form reject a POST that has no dream.csrf field?
Without a token there is no evidence that the POST came from the server's own form; the handler maps `Missing_token to 403 Forbidden.
From the lesson Web services with Dream
-
When does Dream's session cookie carry Secure?
Only when Dream itself received the request over TLS. Dream 1.0.0 does not read X-Forwarded-Proto, and over TLS the cookie is named __Host-dream.session.
From the lesson Web services with Dream
-
Which security headers does the notes service set, and does Dream set them?
X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Referrer-Policy: strict-origin-when-cross-origin and Cache-Control: no-store, all added by the application: Dream sets none of them.
From the lesson Web services with Dream
-
Where does input validation sit in the notes service?
Before any storage call: a 300-character note is refused with 400 before add_note runs.
From the lesson Web services with Dream
-
What is a Dream middleware?
A function from a handler to a handler: it takes the next handler and returns one that can act before calling it, after it returns, or both. In a @@ stack the leftmost layer is the outermost.
From the lesson Web services with Dream
-
What did OCaml 5.0 remove that 4.14 had marked deprecated?
Stream, Genlex, Pervasives, the standalone bigarray library and long-deprecated values such as String.set.
From the lesson OCaml 5 migration, versions and the ecosystem
-
Why does a 4.14 tokenizer stop on OCaml 5.5.1 with Unbound module Stream?
OCaml 5.0 removed Stream. The Seq port, which returns the rest of the sequence instead of consuming it, printed the same seven tokens.
From the lesson OCaml 5 migration, versions and the ecosystem
-
Why must a Seq port of a tokenizer return the rest of the input?
A Stream passed to two functions was consumed by the first, but the sequence String.to_seq builds starts from the same place each time it is read, so take_while returns (word, rest).
From the lesson OCaml 5 migration, versions and the ecosystem
-
In what order does an OCaml 5 migration go?
The removed names first; then shared mutable state and C stubs, which compile cleanly but need Atomic or Mutex and custom blocks.
From the lesson OCaml 5 migration, versions and the ecosystem
-
What is a library's compiler floor, and where do OCaml's security fixes arrive?
The oldest compiler it installs on: 4.14 for Lwt 6.1.2 and 5.2 for Eio 1.6. Security fixes arrive in point releases.
From the lesson OCaml 5 migration, versions and the ecosystem