Question: ```
let () = Random.self_init();;
let _ = Random.self_init ();;
│- : unit = ()

```

It seems "let ()" returns nothing ?

Sincerely!

Answer: 
`let` is the keyword used to define new variables, like in the following construct:

```
let pattern = expr

```

For instance

```
let a = 2

```

assigns the value `2` to the name `a`. (Note this is not a way to assign a value to an already existing variable, but this is another topic).

But the pattern to the left of the `=` sign can be more than just a name. For instance

```
let (a,b) = (42,"foo")

```

defines both `a` and `b`, to be respectively `42` and `"foo"`.
Of course, the types on both sides must match.
Which is the case here: both sides are of type `int * string`.

The expressions to the right of the `=` sign can also be elaborated, for instance

```
let foo =
  let temp = String.make 10 'a' in
  temp.[2] <- 'b';
  temp

```

defines `foo` as the string `"aabaaaaaaa"`. (As a side note, it also ensures that `temp` is local to this code snippet).

Now, let's use both: on the left, a pattern matching values of type `unit`, and on the right, an expression of type `unit`:

```
let () = Printf.printf "Hello world!\n"

```

Which explains the `let () =` construct.
Now, about the `let _`, one simply needs to know that `_` can be used in a pattern as a wildcard: it matches values of any type and does not bind any name. For instance

```
let (a,_) = (42,"foo")

```

defines `a` as `42`, and discards the value `"foo"`. `_` means "I know there is something here and I explicitly say I will not use it, so I don't name it". Here `_` was used to match values of type `string`, but it can match value of any type, like `int * string`:

```
let _ = (42,"foo")

```

which does not define any variable and is not very useful. Such constructs are useful when the right hand side has side effects, like this:

```
let _ = Printf.printf "Hello world!\n"

```

which explains the second part of the question.

**Practical purposes**

Both are used and it's rather a matter of taste whether to use one or the other.

`let () =` is slightly safer as it has the compiler check that the right hand side is of type `unit`.
A value of any other type than unit is often a bug.

`let _ =` is slightly shorter (I've seen this argument). (Note that with an editor that automatically closes parenthesizes, the number of keystrokes is the same ;-)
I'm not an OCaml expert, although let me share something :)

The `let` in OCaml can represent two things:

1. The way you can assign variables;
2. The way you can declare functions or assign functions to names;

Using examples, you can see clearly how it works:
Assigning variables:

```
# let ten = 10;;
val ten : int = 10

# let hello_world_string = "Hello World";;
val hello_world_string : string = "Hello World"

```

Declaring functions:

```
# let sum a b = a+b;;
val sum : int -> int -> int = <fun>
# sum 2 3;;
- : int = 5

```

So, answering the question the difference between let ()= and let \_= is:

At first example, you are declaring a function that doesn't have name, parameters nor instructions that should output an unit. The second example, you aren't assigning to \_, that is OCaml's wildcard, any value.

As we can see below, we can define a function, that will be executed immediatly because we won't be able to call it anymore:

```
# let () = print_string "Hello";;
Hello

```

Or assign to OCaml's wildcard a type and value, or a function:

```
# let _ = 10;;
- : int = 10
# let _ = print_string "Maybe I answered your question :) ";;
Maybe I answered your question :) - : unit = ()

```