let Bindings

A binding associates an identifier with a value or function. You use the let keyword to bind a name to a value or function.

Syntax

// Binding a value:
let identifier-or-pattern [: type] =expressionbody-expression
// Binding a function value:
let identifier parameter-list [: return-type ] =expressionbody-expression

Remarks

The let keyword is used in binding expressions to define values or function values for one or more names. The simplest form of the let expression binds a name to a simple value, as follows.

let i = 1

If you separate the expression from the identifier by using a new line, you must indent each line of the expression, as in the following code.

let someVeryLongIdentifier =
    // Note indentation below.
    3 * 4 + 5 * 6

Instead of just a name, a pattern that contains names can be specified, for example, a tuple, as shown in the following code.

let i, j, k = (1, 2, 3)

The body-expression is the expression in which the names are used. The body expression appears on its own line, indented to line up exactly with the first character in the let keyword:

let result =

    let i, j, k = (1, 2, 3)

    // Body expression:
    i + 2 * j + 3 * k

A let binding can appear at the module level, in the definition of a class type, or in local scopes, such as in a function definition. A let binding at the top level in a module or in a class type does not need to have a body expression, but at other scope levels, the body expression is required. The bound names are usable after the point of definition, but not at any point before the let binding appears, as is illustrated in the following code.

// Error:
printfn "%d" x
let x = 100
// OK:
printfn "%d" x

Function Bindings

Function bindings follow the rules for value bindings, except that function bindings include the function name and the parameters, as shown in the following code.

let function1 a = a + 1

In general, parameters are patterns, such as a tuple pattern:

let function2 (a, b) = a + b

A let binding expression evaluates to the value of the last expression. Therefore, in the following code example, the value of result is computed from 100 * function3 (1, 2), which evaluates to 300.

let result =
    let function3 (a, b) = a + b
    100 * function3 (1, 2)

For more information, see Functions.

Type Annotations

You can specify types for parameters by including a colon (:) followed by a type name, all enclosed in parentheses. You can also specify the type of the return value by appending the colon and type after the last parameter. The full type annotations for function1, with integers as the parameter types, would be as follows.

let function1 (a: int) : int = a + 1

When there are no explicit type parameters, type inference is used to determine the types of parameters of functions. This can include automatically generalizing the type of a parameter to be generic.

For more information, see Automatic Generalization and Type Inference.

let Bindings in Classes

A let binding can appear in a class type but not in a structure or record type. To use a let binding in a class type, the class must have a primary constructor. Constructor parameters must appear after the type name in the class definition. A let binding in a class type defines private fields and members for that class type and, together with do bindings in the type, forms the code for the primary constructor for the type. The following code examples show a class MyClass with private fields field1 and field2.

type MyClass(a) =
    let field1 = a
    let field2 = "text"
    do printfn "%d %s" field1 field2
    member this.F input =
        printfn "Field1 %d Field2 %s Input %A" field1 field2 input

The scopes of field1 and field2 are limited to the type in which they are declared. For more information, see let Bindings in Classes and Classes.

Type Parameters in let Bindings

A let binding at the module level, in a type, or in a computation expression can have explicit type parameters. A let binding in an expression, such as within a function definition, cannot have type parameters. For more information, see Generics.

Attributes on let Bindings

Attributes can be applied to top-level let bindings in a module, as shown in the following code.

[<Obsolete>]
let function1 x y = x + y

Scope and Accessibility of Let Bindings

The scope of an entity declared with a let binding is limited to the portion of the containing scope (such as a function, module, file or class) after the binding appears. Therefore, it can be said that a let binding introduces a name into a scope. In a module, a let-bound value or function is accessible to clients of a module as long as the module is accessible, since the let bindings in a module are compiled into public functions of the module. By contrast, let bindings in a class are private to the class.

Normally, functions in modules must be qualified by the name of the module when used by client code. For example, if a module Module1 has a function function1, users would specify Module1.function1 to refer to the function.

Users of a module may use an import declaration to make the functions within that module available for use without being qualified by the module name. In the example just mentioned, users of the module can in that case open the module by using the import declaration open Module1 and thereafter refer to function1 directly.

module Module1 =
    let function1 x = x + 1.0

module Module2 =
    let function2 x =
        Module1.function1 x

open Module1

let function3 x =
    function1 x

Some modules have the attribute RequireQualifiedAccess, which means that the functions that they expose must be qualified with the name of the module. For example, the F# List module has this attribute.

For more information on modules and access control, see Modules and Access Control.

See also