Why is declaring two variables with the sequence operator ; illegal?

I tried:

let x = 1; let y = 2;;

but is a syntax error. Why is that?


Note I know you can do

let x = 1 let y = 3;;

though thats not my question.

let is for expressions ā€“ something which will resolve to a result. Sequencing is for statements, with no used value but generally having some side-effect (eg. screen output, or modifying a memory cell).

If let x = 1; was accepted, it wouldnā€™t do anything. The scope of x only exists within that statement.

What you want instead is let x = 1 and y = 2 in ā€¦ followed by use of x and y. Or let x = 1 in let y = 2 in ā€¦

Think of building an expression rather than a sequence of statements.

2 Likes

Because itā€™s read as:

let x =
  1; (* this should have type unit but only gives a warning *)
  let y = 2
;;

Your expression doesnā€™t have any value.

Because sometimes you want to write things like:

let x =
  print_endline "hello";
  1
2 Likes

I guess I still donā€™t get it and I donā€™t know what it is. I will try to ask something to perhaps figure out whats confusing me. I guess I donā€™t understand what the difference is between statements and expressions in OCAML. I am also confused when ; vs ;; is usedā€¦thnx for the help. :slight_smile:

I think this sequencing, statements and expression things is still not clear to me at all. Perhaps going through this example would help?

let result =
    let test x = ( p 650; fun y -> ( p x ) + ( p y ) ) in test ( p 4 ) ( test ( p 5 ) ( p 6 ) );;

what on earth is going on there! :frowning:

I thought @zapashcanonā€™s answer was pretty good, but given your confusion, maybe have a look at https://ocaml.org/learn/tutorials/structure_of_ocaml_programs.html#The-sequence-operator , which explains it in further detail.

oh, Iā€™ve read that before of course! :stuck_out_tongue: Though, perhaps I can clarify what confuses me about that document. What does this mean:

The semi-colon ; may be seen as having type unit -> 'b -> 'b ā€” it takes two values and simply returns the second one, the first expression is guaranteed to be evaluated before the second.

What confuses me a lot is that ; ā€œtakes the unit typeā€. I thought to take the unit type one has to pass itā€¦ like:

let f () = 2;;
f ();; (* returns 2 afaik*)

what do you mean it only gives a warning? To me it gives me a syntax error:

# let x =
    1; (* this should have type unit but only gives a warning *)
    let y = 2
  ;;
Error: Syntax error

The () value is the only literal value of type unit, but itā€™s not the only expression of type unit :slight_smile: For example, the function ignore takes a value and returns a result of type unit: ignore 5 (* has type unit *)

Its implementation is: let ignore _anything = ()

As you can imagine, many different functions can return () and therefore so can many different expressions. So saying that the semicolon ā€˜operatorā€™ takes two expressions, one of type unit, and the other of polymorphic type 'b, means that it looks like this:

expr1 ; expr2

ā€¦ where expr1 : unit and expr2 : 'b.

The exampleā€™s incomplete; you need to complete it with an expression; e.g.

let x =
  1;
  let y = 2 in y + 3
;;

results in

Line 2, characters 2-3:
Warning 10: this expression should have type unit.
val x : int = 5
1 Like

I guess your explanation makes sense, after, to my surprise the following code gave me a warning:

# let do_this x = ( add2 5; fun y -> y + 4 );;
Warning 10: this expression should have type unit.
val do_this : 'a -> int -> int = <fun>

although, I guess the code DID work since:

# do_this 2 4;;
- : int = 8

worked.

This are making a lot more sense. But I still donā€™t understand the difference between expressions and statements.

what [quote=ā€œatavener, post:2, topic:4507ā€]
Think of building an expression rather than a sequence of statements.
[/quote]

what is the difference between a statement and an expression?

Statements donā€™t ā€˜returnā€™ a value. E.g. in a language like say Python:

if x == 1: print("One")

This is a statement. It doesnā€™t ā€˜result inā€™ a final value. In OCaml:

if x = 1 then print_endline "One"

This is an expression. It ā€˜results inā€™ a value. In this case () of type unit. OCaml is designed so that almost all syntax is an expression. The standard if ... else ..., the try ... with ..., even for ... in ... do. This is useful because itā€™s more expressive. Different parts of the syntax compose together because everything results in values after all. While other languages had to add special syntax for this (e.g. Pythonā€™s X if COND else Y and C/C++/etc.'s ternary syntax), ML (and Lisp) languages get it from day one.

IMO, a good way to start is to stop using the top level, to write all your code in a file and to wrap all your code in a single ā€œmainā€ function (let _ = ...). Then, you can stop completely using ;; youā€™re only left with let ... in and statements.

let _ =
  let rec fib x =
    if x < 0 then failwith "fib";
    if x < 2 then x else fib (x - 1) + fib (x - 2)
  in
  let print_res x =
    Printf.printf "fib %d = %d" x (fib x)
  in
  let x1 = 5 in
  print_res x1;
  let x2 = 6 in
  print_res x2;
  ()

Once youā€™re at ease with this, itā€™ll be easier to understand. You would just rewrite it like that:

let rec fib x =
  if x < 0 then failwith "fib";
  if x < 2 then x else fib (x - 1) + fib (x - 2)

let print_res x =
  Printf.printf "fib %d = %d" x (fib x)

let _ =
  let x1 = 5 in
  print_res x1;
  let x2 = 6 in
  print_res x2;
  ()

Not sure if you are familiar with Python, but I will try translating your questions to that.

let x = 1 ; let y = 2
(* this fails because let .. = .. must be followed by `in`.

  In the top level / outer scope of a module (such as your file.ml)
  you don't need the "in," but for all the nested let .. you do.
*)

is essentially equivalent to:

x = ( 1; y = 2

and

let x = 1 let y = 3;;
(* with less misleading formatting: *)
let x = 1
let y = 3
;;

is

x = 1 ; y = 2
# aka
x = 1
y = 2

basically ; in ocaml is a very strong binding operator, as opposed to being a statement/expression separator in most other languages.
When you see a ; in OCaml you should read it at "put () around the previous expression, and this one, and make sure that the previous expression returned type unit".

so revisiting your original:

let x = 1; let y = 2
;;

first we need to close the let .. scope:

let x = 1; let y = 2 in y
;;
(* with the implicit grouping/binding order spelled out: *)
let x = ( 1; (let y = 2 in y) )

(* x is now 2 *)

OCaml will still complain, because the type of 1 is int, and youā€™re using ; to denote you want a side-effect and donā€™t care about the value of the expression (since nothing is recording it).

Thereā€™s a function in the standard library called ignore which has signature 'a -> unit, it is basically implemented like this:

let ignore _whatever = ()

We can use it like this to satisfy the type checker:

let x = ignore 1; let y = 2 in y
(* more readable formatting: *)
let x =
    ignore 1;
    let y = 2 in
    y

Hope that helps :slight_smile:

What does this mean? I think Iā€™m starting to understand better that everything in OCAML results in a value (even though the distinction between expressions and statements is still not clear to me). I guess what I am confused is that I ONLY know python really. So Iā€™m having a hard time understand what OCAML does. I thought python always returned values too. The way I thought about python is I have a bunch of statements (pretty much one per line usually) and they get executed and return a value. So for me everything is a statement in the programming I am used to. So what I am trying to understand is how do things work in OCAML. It seems once the distinction btw statements and expressions is clear everything would make sense to me.

me asking for clarification on the precise difference between statements and expressions: What is the difference between statements and expressions in OCAML?

I donā€™t think this is right. Correct me if Iā€™m wrong. Check this:

Python 3.7.3 (default, Mar 27 2019, 16:54:48)
[Clang 4.0.1 (tags/RELEASE_401/final)] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> print("One")
One
>>> x = print("One")
One
>>> x
>>> print(x)
None
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined

it seems that x DOES have a value. The print statement is a function and it returns None afaik. I tried printing y to emphasize that x had to be assigned.

print("One") is an expression. if x == 1: print("One") doesnā€™t.

>>> x = 1
>>> y = (if x == 1: print("One"))
  File "<stdin>", line 1
    y = (if x == 1: print("One"))
          ^
SyntaxError: invalid syntax

2 Likes

Iā€™m confused about this. I can easily do:

# let x = 3;;
val x : int = 3

not only in top level but in my .ml file to. I can define a variable and then use it later if I need to. My question makes me feel there is something fundamental about OCAML or perhaps fp programming I donā€™t understand.