let <id1> : <type1> [ <- <expr1> ], ..., <idn> : <typen> [ <- <exprn> ] in <expr>
The optional expressions are initialization; the other expression is the body. A let is evaluated as follows. First <expr1> is evaluated and the result bound to <id1>. Then <expr2> is evaluated and the result bound to <id2>, and so on, until all of the variables in the let are initialized. (If the initialization of <idk> is omitted, the default initialization of type <typek> is used.) Next the body of the let is evaluated. The value of the let is the value of the body.
The let identifiers <id1>,...,<idn> are visible in the body of the let. Furthermore, identifiers <id1>,...,<idk> are visible in the initialization of <idm> for any m > k.
If an identifier is defined multiple times in a let, later bindings hide earlier ones. Identifiers introduced by let also hide any definitions for the same names in containing scopes. Every let expression must introduce at least one identifier.
The type of an initialization expression must conform to the declared type of the identifier. The type of let is the type of the body.
The <expr> of a let extends as far (encompasses as many tokens) as the grammar allows.