Double semicolon peculiarity

I can’t seem to find the exact meaning of a double-semicolon in an interaction with ocaml. The material here seems to suggest that if they’re placed properly, they should have no impact on the code.

But consider the following:

# let a = 4;;
val a : int = 4
# let b = 2;;
val b : int = 2

Now let’s add a comment:

# let a = 4;; (* define the name a *)
val a : int = 4
# let b = 2;;
val b : int = 2
# 

Everything still works as expected (both in the toplevel and in a file).
But when that comment extends to a second line, something peculiar happens:

# let a = 4;; (* define the name a
val a : int = 4
# for later use *)
Warning 2: this is not the end of a comment.

And it’s not that multi-line comments are disallowed, because this works:

# (* define the name a
* for later use *)
  4;;
- : int = 4
# 

and so does this:

# let a = 4 (* define
* the name a *);;
val a : int = 4
# 

There’s a clear lesson here – it’s the old joke:

Patient: Doc, it hurts when I do this [moves arm around]
Doctor: Don’t do that!

I should avoid the double-semis. But the quizzical part of me asks, “What the heck is going on with the parsing here?”

Can anyone enlighten me?

# print_endline "NB.";; total nonsense here
NB.
- : unit = ()

the toplevel reads in lines until it sees a ;;, then it ignores everything after that and tries to interprets everything before it as valid OCaml code. The ‘comments’ that you had after ;; were just ignored.

Thanks. It seems like a peculiar choice to me, given the value of the double-semicolon “To split the code to ease debugging;” (as mentioned in the text I referenced in the original question), but at least it makes consistent sense.

It’d sure be nice if its meaning were more consistent, though. If I have a file a.ml containing

let a = 4;; (* define a    
                   for later use *)
print_int a; print_endline ""

then I get

% ocaml f.ml
4
% ocaml < f.ml
        OCaml version 4.01.0
#                    for later use *)
  print_int a; print_endline ""r use *)
Warning 2: this is not the end of a comment.
    
Error: Syntax error
# 
%

I grant that the second form might not be what folks should do, but it’s a little odd (to me) that it’s inconsistent with the first. In other words, the “ignore everything after double-semi” seems to be only a part of the interactive toplevel, and the meaning of double-semi elsewhere is something different. I’m sure that’s a deliberate choice; I’m just not sure why it would be a wise one.

That value isn’t seen interactively – the examples given immediately after that part of the text are all of files. I doubt the value myself; the effect for someone learning OCaml is that inserting ;; makes syntax errors go away:

let () = print_endline "hi"

print_endline "there"

let s = "!" in print_endline s

first error: “Syntax error” on line 5, at the in. A mistake with let ... in ... is always the worst, as far as OCaml error messages go.

let () = print_endline "hi"

print_endline "there"
;;
let s = "!" in print_endline s

second error, line 1, at print_endline: This function has type string -> unit. It is applied to too many arguments [meaning the folllowing print_endline and "there"]; maybe you forgot a ‘;’

result:

let () = print_endline "hi"
;;
print_endline "there"
;;
let s = "!" in print_endline s

which would look a little better with ;; at the end of the previous lines, but still good style would be

let () =
  print_endline "hi";
  print_endline "there";
  print_endline "!"

which is completely different.

I came up with these rules to follow that fixed my wanting to use ;; in source files:

  1. don’t use “let … in” in toplevel code
  2. write “let () =\n do_this ();\n do_that ()” instead of
    "do_this (); do_that ()" in toplevel code
  3. treat ; like an infix operator rather than a terminator or an
    optional separator. Type it in expectation of a right-hand-side.

‘toplevel’ meaning, not as part of the body of some other expression.

Oh you added to your comment.

Yeah, with that in a file, that’s quite weird. It shows that ;; is really not considered to be a valid and normal part of the syntax. I’d treat is more as a hack so that code can be copied and pasted to either a file or to ocaml interactively.

Good style again should be:

let a = 4
  (* <-- unnecessary blank line *)
let () = print_int a; print_endline ""

I certainly see that that’s better syntax. I think that “It shows that ;; is really not considered to be a valid and normal part of the syntax” is probably correct, but I’d prefer that in the languages I use, things that are not a valid part of the syntax be flagged as errors rather than treated inconsistently.

I mostly really enjoy my limited use of OCaml, but things like this make me crazy… it’s as if someone said, “What can we add to the design to make sure that it’s really difficult to get started and understand what’s going on?”

I suppose that it could be worse. It could be PHP. :frowning:

;; is a valid part of the syntax – but is an unnecessary one and it is not supported in the REPL. ;; should be read as a prefix meaning “the following is a item-level construct”. It should really be written like:

;; let x = something
;; do_something ()

If you don’t use standalone expressions at item-level – which are considered quite bad style these days – then OCaml can already unambiguously tell what is item-level and what isn’t. So this is not a particularly useful construct. It only has any value because the parser can’t always unambiguosly tell what is item-level in malformed code, so sometimes it improves error messages.

In the REPL this construct is not supported because ;; is instead used to indicate end of input to the REPL.

2 Likes

I don’t think you’re wrong about this one. The double-semi is something of a wart on the language.

Sadly, this “wart” is also useful in introducing the language to beginning
students who are used to languages like Scheme, where it’s easy to know
when an expression is “finished”, and the REPL can respond confidently. I’d
hate to conclude that introducing (parts of) OCaml to beginning students is
a bad idea, but it clearly requires some subtelty.

Anyhow, thanks to everyone for helping clarify things for me.

It might be nice to make a version of the REPL that didn’t require double underscores at all. Then we could just never mention this to people. e.g., you could have a case where you just type return to end an expression, and do shift-return when you don’t want to end the expression.

I wonder if this is doable in utop. I’m not sure whether shift-return is available. @jeremiedimino, do you know?

3 Likes

There really was a question asked, that ;; was the answer to, but it was “What can we add to the design to get an interactive interpreter to stop waiting for more input?”

For example, given just

print_endline "Hello"

do you want to print that right away? Or do you want to start a |> chain on the next line to do something with the () ?

print_endline "Hello"
|> ( = ) ();;

If you’re entering something to see what it evaluates to, like 1 + 2 , then you can always tack another operator onto it. Without some rule to know when to actually do anything, the interpreter can’t know when to stop waiting for more input.

Python has a similar problem: definitions don’t have an ‘definition is over’ indicator in the language. So if you enter

def hello():
    print "Hello, world!"

you could enter ten newlines and then type

    print ":)"
hello()

and that would be valid in a file, but quite annoying interactively. Python has an interpretive-only rule then that a blank line ends input. Which breaks copy&paste of definitions of blank lines in them.

>>> def hello():
...     print "Hello, world"
... 
>>> # oh no, I can't add that second print now

If ;; had never been tried and was presented today as an alternative to a python-style rule for ocaml input, I can’t say I wouldn’t prefer ;;. But today with the level of confusion around it, and with it appearing all the time in books about the language even though you don’t see it in source, I think a REPL with a Python rule is worth trying.

1 Like

As a new comer to Ocaml having played with REPLs of interpreted languages, I tend to usually forget the ;; until I come to the point of “Nothing happens. What’s going on? Ah, yes, go and interpret this, REPL” And I think it’s a good thing. The courses of Ocaml at FUN mainly focused on writing a small file and interpret it on the fly, not in a REPL like environment.

For people mostly accustomed to C-like languages, I guess the first idea is to think that they simply have to convert their single semi-colons into double semi-colons (and the first message gives me this impression), which is very delusive and leads to surprises.

Tuareg interactive mode already allows you to use S-return to submit an expression to the REPL. If you press return it notifies you that ;; or S-return is needed to process what you typed. When editing an expression, it may well be the case that you want to introduce an inner line break; thus one requires that expressions are only processed when the “cursor” (point in Emacs parlance) is after the ;; if there is one — but S-return is more convenient in this case.

If you use Emacs, you do not need to speak of ;; at all — as said above, it will be in the message when pressing return and double colon will be shown in the buffer but we could add an option to hide that if it makes a difference for your students.

neat. You can get to this with M-x run-ocaml . This mode is incompatible with utop, but (while looking to see how I’d add a similar command to utop I realized that) M-x utop also has a command like this, bound by default to C-j (control-j).

Inside the terminal, you can’t distinguish shift-return from return. However you can distinguish meta-return from return and utop already binds meta-return to inserting a newline without ending the expression.

BTW, you can add this in your ~/.ocamlinit to make return automatically insert the double semicolon:

LTerm_read_line.bind
  [ { control = false; meta = false; shift = false; code = Enter } ]
  [ UTop.end_and_accept_current_phrase ]

7 Likes

i use both the python REPL and utop. i can say copy and paste is a constant source of frustration with python, and the ;; in utop seems to work much better for that. i finally settled on pasting python code from vim with vim-cellmode which uses intermediate files in order to circumvent the problems.

I’ve known for years that the following is normal and expected, but I still find it quite annoying:

$ cat p.ml 
print_endline "plop";; print_endline "hello";;
$ ocaml p.ml 
plop
hello
$ ocaml < p.ml 
        OCaml version 4.09.1

# plop
- : unit = ()
# 

This is reported in this issue. It doesn’t seem people consider it as “normal and expected”.

1 Like

Wow! Thank you! I was kinda convincing myself I was the only one really annoyed by this…

And, I didn’t expect that: this issue to be discussed here in 2017 and then an issue about it on Github open in 2019!

Now I do have hope that it’ll be eventually fixed.