Terminate a line with ; or ;; or in or nothing

One of the difficult thing in learning ocaml is how to end a line.
There are 4 options : ; or ;; or in or nothing.
I found few rules of thumb.
I try to use in wherever i can.
I use ;; when i want to do things sequentially.
I use ; after a mutation ie a:=b which is not the last one of the expression.
I found when i switch the order of the let in my code i can use more termination by “in”, i put functions first then variables.
Sometime i have to add begin , end.

I also break my head over the definition of a statement and an expression. But that’s language.
The rules for programming in Scheme are clearer for me at this moment because brackets have clear endings.
Feel free to elaborate.

1 Like

Ocaml is not a line-oriented language. It is expression-oriented, and in the toplevel, you can terminate expressions with “;;”

I think it would be worth reading the OCaml manual – the section on syntax – to understand the syntax of the language. Also, I’ll note that there are precious few line-oriented languages around. For instance, while Golang “appears” line-oriented, it actually is not – and the slam of line-orientation is achieved by some particularly nasty and hilarious unnatural congress between the lexer and parser. All of this is to say that really, I think it’s worth actually examining the grammatical rules of the language, no matter which language you’re trying to learn.

ETA: “in” definitely does not terminate a line. It opens a scope within which a binding will be active (as in “let x = e in M”), and whether there are line-breaks is strictly irrelevant.

2 Likes

More or less what Chet said, just to add—you shouldn’t need to use rules of thumb and heuristics. Once you learn the rules for each of those syntactic elements—they are fairly simple but do need to be internalized—you will see that every situation has clear rules.

The rules for ;; are a bit more debatable but basically, you never need to use it in source code. Only need it in the REPL.

1 Like

If you use GNU Emacs, then a good way of familiarising yourself with the syntax might be to use gopcaml-mode - the structural highlighting can help to explain how the text in your buffer is parsed into expressions:
gopcaml_full_syntax_support

I tell my current mental proces. I reason in a procedural way, i put everywhere ;; at the end of the line.
Then i start to think can i remove some “;;” and use “;” or “in” instead.
Example program below , i cannot just remove ;; here or there or the compiler will spit out errors.
Feel free to correct without changing the meaning.

let a=ref 0;;
let b=ref 0;;
let f x=x+ !a;;
let g x=x+ !b;;
let c x=(f x) + (g x);;
a:=1;;
b:=1;;
Printf.printf "%d\n" (c 10);;
a:=2 ;;
b:=2 ;;
Printf.printf "%d\n" (c 10);;

This is part of the issue. In ocaml there is no distinction between statements and expressions. Statements are expressions in whose value you are not interested. This and the associated syntax can be an initial speed bump for those who come from a brace-structured language (C, C#, Java and so forth) or an indentation/whitespace structured language (Python).

The structuring of those languages is well suited to code which may perform side effects. However ocaml expression syntax relies much more on operators and operator precedence. The results are intuitive most of the time in relation to the composition of expressions (ocaml is a functional language after all) but less so when functions are evaluated for their side effects: that is, where the value to which a function evaluates is ignored or that value is unit. Take this:

  let x = "Hello world"
  print_endline x

At first sight it looks OK it but it won’t compile. Furthermore, before you even get to compiling it, a decent editor is going to try to format it as something like:

  let x = "Hello world"
             print_endline x

The proximate cause is that, because of the precedence of function application, this is in fact:

  let x = ("Hello world" print_endline x)

namely function application of a string which is clearly impossible.

; is for sequencing side effects, so you might try:

  let x = "Hello world" ;
  print_endline x

but that won’t work because “Hello world”, and so ‘x’, doesn’t evaluate to unit.

Instead let is the operator to express the intended meaning.

  let x = "Hello world"
  let _ = print_endline x

Or if ‘x’ is not intended to be a top level variable:

  let x = "Hello world" in
  print_endline x

The ; operator used with side-effectual functions can have other gotchas until you get used to it. For example this might look OK:

  if true then
    do_this () ;
    do_that ()
  else
    do_the_other () ;

but it won’t compile. This is because the code is in fact this:

  (if true then do_this () ) ;
  (do_that () else do_the_other () )

meaning that because of the sequencing operator the conditional expression ends with ‘do_this ()’ and ‘else’ is left dangling. What the programmer really meant was:

  if true then (do_this () ; do_that () )
  else do_the_other () ;

Furthermore, this will be fine because let has even lower precedence than the semi-colon operator:

  if true then
    let x = 5 in
    do_this x ;
    do_that ()
  else
    do_the_other () ;
  yet_more_stateful_computation ()

But this will never execute yet_more_stateful_computation, because despite appearances its application is part of the let expression within the else block:

  if true then
    do_this ()
  else
    let x = 5 in
    do_that x ;
  yet_more_stateful_computation ()

What the programmer really meant was:

  (if true then
     do_this ()
   else
     let x = 5 in
     do_that x) ;
  yet_more_stateful_computation ()

In short, particularly when dealing with side effects, as others have said there is no substitute for learning the rules on operator precedence and associativity. In the end it will become second nature.

On ;;, as others have also said, don’t use it except at the REPL

4 Likes

Quick 2¢ here, this is the kind of thing I learned mostly by letting ocamlformat (integrated with my editor) show me what it thinks is best. Like parentheses, I type in a few more than I think might be required and ocamlformat removes everything superfluous. With my settings anyway, it keeps the ;; after multi-line top-level definitions but removes it after single-line ones, so it seems to be useful mostly just for clarity with human readers.

2 Likes

ocamlformat gives really good hints. I use it together with neovim.
Also the precedence of “operators” is important as cvine pointed out.
In program below you are not allowed to remove any ;;
It is not at first intuitive why it was ok to remove the others.

let a = (ref 0)
let b = (ref 0)
let f x = (x + !a)
let g x = (x + !b)
let c x = (f x + g x );;
a := 1;;
b := 1
let _ = (Printf.printf "%d\n" (c 10));;
a := 2;;
b := 2
let _ = (Printf.printf "%d\n" (c 10))
1 Like

There are a few clear rules that might help you decide if you need to use ; of ;;:

Instruction separator

;; is used to separate instructions at the top level of a file, that is

  • global let <name> = <expr> inscriptions (without in)
  • open and include statements
  • type, exception, val declarations
  • module, definitions / declarations
  • (and I probably forgot some)
  • and simple top-level expressions.

But there is a handful of handy exceptions: ;; are optional before:

  • a global let
  • an open or include statement
  • a type, exception, val, module declaration / definition
  • (the ones I have forgotten earlier)
  • after the final instruction of the file

That is almost all of the time ! Except when you use top level expressions (and if you are allergic to semicolons you can just use let () = <expr> to benefit for the exception).

Sequence operator

; is not just a syntactic hint in OCaml, it is an operator of type unit -> 'a -> 'a that allows one to chain expressions together. It is conveniently named the sequence operator.

Example

For example, the following snippet:

open Graphics ;;

open_graph ”␣300 x400” ;;

let radius = 10 ;;

let rec push x y =
  let event = wait_next_event [ Button_down ] in
  let mx = event.mouse_x in
  let my = event.mouse_y in
  let dist = (mx − x ) ∗ (mx − x ) + (my − y ) ∗ (my − y ) in
  if not ( dist < radius ∗ radius )
  then push x y
  else exit 1 ;;

set_color black ;;
fill_circle 100 100 radius; push 100 1 0 0 ;;

can be stripped of most of its semicolons:

open Graphics;;

open_graph ”␣300 x400”

let radius = 10

let rec push x y =
  let event = wait_next_event [ Button_down ] in
  let mx = event.mouse_x in
  let my = event.mouse_y in
  let dist = (mx − x ) ∗ (mx − x ) + (my − y ) ∗ (my − y ) in
  if not ( dist < radius ∗ radius )
  then push x y
  else exit 1 ;;

set_color black ;;
fill_circle 100 100 radius; push 100 1 0 0

And a few more with dummy letexpressions:

open Graphics

let () = open_graph ”␣300 x400”

let radius = 10

let rec push x y =
  let event = wait_next_event [ Button_down ] in
  let mx = event.mouse_x in
  let my = event.mouse_y in
  let dist  = (mx − x ) ∗ (mx − x ) + (my − y ) ∗ (my − y ) in
  if not ( dist < radius ∗ radius )
  then push x y
  else exit 1

let () = set_color black
let () = fill_circle 100 100 radius; push 100 1 0 0

You can find more information in that great blogpost.

3 Likes

If you look at the program i posted you see there are only ;; before a := statement.
For the moment I use those kind of heuristics.
I had put () in the code to have a clear begin,end and operator precedence.
But it looks to me as ;; is scattered somewhere in the middle of the code for unknown reasons as i don’t see which rule i can apply to which line of the code i posted.

Yes, because these are “simple top-level expressions”.

:= is an operator that returns unit ('a ref -> 'a -> unit) so you can replace them by let () = a := statement to get rid of the ;;

And chain them:

let () =
  a := b;
  c := d

The few rules I mentioned in my previous post should cover 99% of the cases (100% maybe but I may have forgotten some toplevel-available instructions). No heuristics needed :slight_smile:

2 Likes

And don’t hesitate to take the time of reading the lengthier blogpost. It is a very good introduction to the basis of OCaml program structure:

https://www2.ocaml.org/learn/tutorials/structure_of_ocaml_programs.html

Or in french:
https://www2.ocaml.org/learn/tutorials/structure_of_ocaml_programs.fr.html

Thanks, now i start to get the grasp of it. Now I made some clean code, which is for me clear ,logical , and understandable and no ;; and only ; for a sequence.

let c f g x = (f x + g x )
let a =   (ref 0)
let b =   (ref 0)
let f x = (x + !a)
let g x = (x + !b)
let () =  (a := 1; b := 1)
let _ =   (Printf.printf "%d\n" (c f g 10))
let () =  (a := 2;b := 2)
let _ =   (Printf.printf "%d\n" (c f g 10))

When i learn more about the syntax i can remove () as needed.

Great ! (in this case you should be able to remove all of them except fro the (c f g 10) ones)

1 Like

To enjoy even clearer and less ambiguous syntax, I recommend using neither ; nor ;; for anything, and just doing everything with let. x ; y is just shorthand for “throw a unit away”, and can always be replaced with let () = x in y. Once you’ve internalized where values get bound to units with let, then you can start reintroducing ; when you want to be more concise:

let c f g x = f x + g x
let a = ref 0
let b = ref 0
let f x = x + !a
let g x = x + !b
let () = let () = a := 1 in b := 1
let () = Printf.printf "%d\n" (c f g 10)
let () = let () = a := 2 in b := 2
let () = Printf.printf "%d\n" (c f g 10)
2 Likes

Note that neither line breaks nor ;; nor parens are necessary to determine the scope of the body bound in top level let, as these are delimited by the start of the next let. So, for example, these are equivalent:

let a = ref 0
let b = ref 0
let a = ref 0 let b = ref 0

Currently i’m breaking my head over the fact that program below, in all it’s simplicity does not compile.
Altough it looks good to me:

let _=
  Printf.printf "%s" "Begin";
  let x=ref 0;
  Printf.printf "%s" "End"

The error given by the ocamlformat & ocamlopt compiler is close to worthless:
ocamlformat: ignoring “./test.ml” (syntax error)
File “./test.ml”, line 5, characters 0-0:
Error: Syntax error
File “test.ml”, line 5, characters 0-0:
Error: Syntax error

It is because a let that is not top-level always needs a matching in
You do not end a variable binding with a ;.
The correct code is :

let _=
  Printf.printf "%s" "Begin";
  let x=ref 0 in
  Printf.printf "%s" "End"
4 Likes

Program below compiles fine,

let _ =
        let c f g x = f x + g x in
        let a = ref 0 in
        let b = ref 0 in
        let f x = x + !a in
        let g x = x + !b in
        let () = let () = a := 1 in b := 1 in
        let () = Printf.printf "%d\n" (c f g 10) in
        let () = let () = a := 2 in b := 2 in
        let () = Printf.printf "%d\n" (c f g 10) in
        ()

Indeed!

fwiw, I find this structuring of the let’s a bit clearer:

let () =
        let c f g x = f x + g x in
        let a = ref 0 in
        let b = ref 0 in
        let f x = x + !a in
        let g x = x + !b in
        let () = a := 1 in
        let () = b := 1 in
        let () = Printf.printf "%d\n" (c f g 10) in
        let () = a := 2 in
        let () = b := 2 in
        let () = Printf.printf "%d\n" (c f g 10) in
        ()

The key difference here just being that each unit values expression gets it’s own line.