Tutorials/Intros: an alternative approach

The tutorials being developed by the ocaml.org team are wonderful, but I actually prefer something a little different, so I’ve started writing some stuff on the following (loose) principals:

  1. Starting with a repl is a mistake. The whole ‘;;’ business is an impediment, and furthermore the repl is misleading: it prints the type and value of the forms it evaluates, except for let definitions, in which case it prints the definition, which is not the value of the let form. So for me at least it is better to start with code in a file and a simple makefile for compiling. The repl is something you use for exploratory programming after you’ve learned the language.

  2. Failure is edifying. Most tutorials give only examples that work. But users like to explore, and discovering what does not work is just as valuable as discovering what does work. Case in point: let forms. It’s trivially easy to write let forms that do not work and produce totally opaque (for newcomers) error messages. (Google “Henry Petroski” for more on the importance of failure in engineering.)

So I’ve started writing a bunch of small demo source files with liberal explanatory comments, each with a simple makefile and a test file (using ounit2), the intention being that the user can tweak the files, run make, and see the results, quickly and easily.

Example: let forms are surprisingly complicated in OCaml. One of the first things I did getting started was try some let stuff, and was immediately stymied. For example, I can write 7 as a top level expression; why can’t I write something like the following?

let f = fun x -> x + 1
2

Of course its obvious if you know OCaml; but even after you learn that whitespace is not a delimiter, the error message is mystifying:

1 | let f = fun x -> x + 1
                         ^
Error: This expression has type int
       This is not a function; it cannot be applied.

Why on earth is OCaml telling me the obvious? Well, because of type inference, etc. Similar puzzling errors and warnings happen when you start recklessly using the semicolon to form expression sequences.

So in short, I’ve started out by working through various ways that let can be used, including ways that one might expect to work but don’t, and trying to find good explanations for the errors.

Here’s one I still don’t understand:

let x = 1 in () (* ok, evaluates to () *)
let y = 2 in () (* syntax error on 'in' *)

I can’t explain exactly why this error occurs.

In any case, here’s the link if you’d like to take a look at what I’ve got so far (bear in mind this is a WIP). Feedback welcome.

(note that it is on the dev branch)

A case where I think this approach is particularly helpful is “expression closures”, see https://github.com/obazl/demos_obazl/blob/dev/ocaml/letforms/closures/a.ml

6 Likes

You might instead see this as a good reason to promote formatters. Some compilers emit warnings for misleading formatting, but I guess it would make OCaml’s frontend unnecessarily more complex and harder to maintain.

1 Like

I like this idea but I would suggest using dune and Alcotest instead of makefiles and OUnit. Arguably the former tools are more prevalent in the OCaml ecosystem than the latter, and we would like to encourage using tools which users will be more likely to interact with in the future on OCaml projects.

8 Likes

I believe that application isn’t between expr but between simple_expr and that does not include the let binding: parsing/parser.mli#L2475

First the syntax error in

let x = 1 in () 
let y = 2 in () 

is on the let. The reason is indeed only simple expressions may appears as argument of functions.
thus one would need to add parentheses around the local let definition

let x = 1 in () (let y = 2 in ())

to avoid the syntax error. Or if the aim was to sequence two toplevel expressions, this is the use case for ;;:

let x = 1 in ();;
let y = 2 in ()

Merging the two expressions into one toplevel expression with ;` works too

let x = 1 in (); let y = 2 in ()
3 Likes

Looks like we have a discrepancy between the repl and the compiler.

# let x = 1 in ()
  let y = 2 in () ;;       (* on my terminal 'let' is underlined *)
Error: Syntax error

In a.ml, compiled:

28 | let y = 2 in ()   (* on my terminal ^^ is aligned under 'in' *)
               ^^
Error: Syntax error

Since we get ^^ and not ^^^ I’m assuming the syntax error is with in (rather than a misalignment of ^^.

Also parens don’t help:

# let x = 1 in () (let y = 2 in ()) ;;
Error: The constructor () expects 0 argument(s),
       but is applied here to 1 argument(s)