Question on order of execution/evaluation

I wrote program below,

module A= struct
    let f1:(unit -> unit)= fun() -> print_string "\n A \n"
end 

module B= struct
    let f2:unit = print_string "\n B \n"
end

let (_:unit)=
    let ()= A.f1 () in 
    let ()= B.f2 in 
    let () =B.f2 in 
    let () =A.f1 () in
    ()  

And it produces as output :

B

A

A

Which is for me not expected. [ I expected A B B A ].
Can someone explain what is happening ?

Try again with a void main function.

You’ll get “B”, because your B module does not define a function. It defines f2:unit, which is return after print_string is evaluated.

This is done before the main function is started.

Best regards

1 Like

I have difficulties in reasoning without a main, that’s why i never use it. (i’m in a learning fase)
main ;; is just sequential just like a C program. No problems to be expected.
But here i thought I print 4 times.
If i understand because f2 is not a function it is only evaluated once.
But it thought i had called it twice ?

f2 has already finished evaluation and yielded a value by the time you defined it in module B, so print_string "\n B \n" is only run once.

2 Likes

What I’m calling main is your:

let (_:unit)=
    let ()= A.f1 () in 
    let ()= B.f2 in 
    let () =B.f2 in 
    let () =A.f1 () in
    () 

Since f2 is (), let () = B.f2 is the same as let () = ().

module A : sig
    val f1 : unit -> unit
end = struct
    let f1 () = print_string "\n A \n"
end

module B : sig
    val f2: unit
end = struct
    let f2 = print_string "\n B \n"
end

let main () = 
   let () = print_endline "Starting main…" in
   let () = A.f1 () in
   let () = B.f2 in
   let () = B.f2 in
   let () = A.f1 () in
   ()

let () = main ()
2 Likes

Remember that OCaml is eager. It tries to evaluate every value you give it as soon as you define it.
Toplevel let is a keyword that’s used to define both values and functions.
let x = 4 is a value
let f y = x + y is a function, because y is a parameter.
A function is a value anyway, the above is the same as let f = (fun y -> x + y).
When OCaml sees a function, it says “I have evaluated this value to <fun>, I am satisfied with this much evaluation. I won’t look and evaluate what’s inside it until it’s fully applied”.

Now when we add side-effects to the mix, evaluating a side-effect means running it.
Most side-effects evaluate to unit after running. Let’s look at the eager evaluation again:
let x = side_effect(), nothing is stopping OCaml from evaluating and producing the side-effect. Once it’s evaluated it’s substituted with a unit and the side-effect is “done”. x is now just a unit value, and you can use it anywhere unit is expected.
let f y = side_effect() is the same as let f = (fun y -> side_effect()), OCaml sees the fun and evaluates to <fun> without looking inside and running the side-effect.
To make OCaml evaluate what’s inside the function, we need to fully apply it. Every time we fully apply it, OCaml evaluates what’s inside, and we’re able to run the side-effect again.

That’s why if you want to do a side-effect more than once, you add a parameter… usually unit… to prevent OCaml from running the effect at definition site, and instead force it to run the effect on every application site.

And that’s why when you define your last definition in the file, usually let _ = ... or let () = ..., it gets evaluated without you asking for its value in a later computation (by perhaps giving it a name and using that name, instead of _).

3 Likes