Top level expression needs to be an union

I seem to have overlooked the obvious, and this might be a typical beginner issue.

1 Like

Since your addition function returns an integer you have to ignore the result in the function call.

Either by using this:

let _ = addition(10, 4)

or that:

addition(10, 4)->ignore

Lets say I would like to print it, could I just pipe it into a print function?

And this seems not to be a very precise error message?

The error is telling you that the top-level (the outermost scope) expects an expression of type unit (similar to void), but you’re returning an int because of the call to addition.

As you mention, you could turn the result of the function call into unit by either logging the result, passing it to ignore (which is simply a function that takes any value and returns unit), assigning the result of the function call to a variable or some other approach.

I love this question because I always find it difficult to explain this to beginners I am onboarding. Even though I have been using ReScript for almost two years, I still find the error message difficult to understand. Also, sometimes like you, I get confused for several minutes before I realize the problem.

// A.res
let a = 4

generates

// A.bs.js
var a = 4;

export {
  a ,
}

However,

// B.res
let _ = 4

generates an empty JS file

// B.bs.js

and

// C.res
4

generates a compilation error:

[E] Line 1, column 0:
Toplevel expression is expected to have unit type.

and this message tries tell you that you are violating of 5 or more different rules that are in play here.

  1. Expression: every expression is terminated by a newline or a semicolon.
    The latter is not documented - it shows up as an error message when you try to write a function with multiple expressions on a single line without a block scope. Try that. That’s how I found out.
  2. Module’s Top level scope:
    a. The pair of curly braces { } define a block scope. Block Scoping
    b. A module reuses the block scope with the following syntax: module C = { ... } to define it’s own scope.
    c. A .res file is basically a module without the block scope syntax. Module and Files

So the top/outermost level scope of a .res file translates to the scope where a and b live as per the below diagram:

module C = {
  let a = 1
  let b = { 1 + 1 }
  {
    let c = 3
    let d = { 
      let e = 5
      e - 1
    }
  }
}
  1. Return type yielding: You can yield a unit return type by
    a. using the let _ binding: let _ = 3 the return type is unit instead of int
    (you need to understand a let binding, more on that below)
    b. using the ignore() function on any expression: 3->ignore or ignore(3)
    (you need to understand function and the pipe syntax)

Which brings an attentive newbie to ask me this question: is the type of the let c = 3 binding unit type?
I don’t really know because the docs never say what is the type of the binding. I know the type of c is int, but the newbie specifically asked whether the binding itself is an expression and is the type of that expression unit? It should be, because writing the let c = 3 effectively resolves the error Toplevel expression is expected to have unit type. and without a subsequent newline to the binding, you can’t say that the next line is empty so it is unit effectively, just like an empty module is unit effectively. All the docs say, is let binds values to names. Can someone help me here?

Thus as you can see, this is a very tricky thing for me to explain to new folks for two reason.
One this is a very likely problem for someone new to programming to encounter in ReScript - it’s not hard to create a new .res file and say 3+4 like you’d do in a Python REPL. And two, this is a very overloaded error message to learn at the very beginning especially for curious minds.

And sometimes I myself forget what the error message is trying to tell me, if I’m thinking about the business problems and I need to change my thinking gears.

Even so, the ReScript Language Documentation is excellently done. It explains a lot of things in a concise manner. Without it I would not have been able to understand the intricacies. The only language doc that explains the language even more concisely would be the Gleam Language Tour.

2 Likes

This explanation looks pretty good:

Screenshot 2023-09-15 at 01.57.55

2 Likes

You know how WIRED channel on YouTube tries to explain stuff at 5 levels of difficulty, this explanation works for levels 1-3. And is a fairly better explanation in simple terms than mine where i tried to lay out all the rules to illustrate the problem.

1 Like

My favorite way to think of it: in a functional language you’re expected to make (mostly) pure functions. Since pure functions don’t do anything, if you call one and don’t set it to anything the compiler is letting you know of your mistake!

I’ve just remembered we’ve been discussing the error before. Maybe we should really improve the error message?

1 Like

To have something like

 9 │ 
10 │ let cloneInTemp = (temp: string): string => { 
11 │   **cd(temp)**
12 │   exec("git clone git@github.com:myorg/myrepo.git")
13 │ }

The return value of the highlighted expression is not used.
Assign the value to a variable, or ignore it using `_` (underscore) to silence the error.
Example:
let _ = cd(temp)
9 Likes