F# inherits the core of the features associated with its functional-first nature from ML and OCaml. This means that its chief fashion of expressing computations is via the definition and application of functions.
The ability to define and apply functions is a common feature of many programming languages. However, F# follows ML and other functional programming languages in treating functions similarly to, say, numeric values. The ways of treating functions in F# go well beyond the limits usually associated with stored-program computer concept:
Computations with functions have the main form of evaluating expressions as opposed to making assignments to variables. Expressions do not carry the stigma of ever-changing values stored in rewritable memory. When the function triple x
is applied to argument value 3
, it evaluates some inner expression and returns 9.
We are sure that this result is consistent, may be repeatedly reproduced, and may change from 9
only if the argument value changes from 3
to something else.
Currying is a method of converting a function of many arguments evaluation into an equivalent evaluation of a sequence of functions of a single argument each. Partial evaluation binds one or more first arguments of a curried function, effectively producing a new function of fewer (non-bound) arguments.
Why bother giving a name to a function that is passed to a higher-order function or is returned from it? For the sake of succinctness F# allows using generic fun
and function
forms of function definitions that will not be invoked from elsewhere, hence the name is omitted.
A frequently used example of recursive function (https://en.wikipedia.org/wiki/Recursive_function) implementation is a divide and conquer algorithm (https://en.wikipedia.org/wiki/Divide_and_conquer_algorithms) when a problem gets broken into a few of the same problems with less dimensions, so the same solving function can be applied. This breaking down continues until solutions get simple, and then smaller solutions are combined back to in the solution of the original size.
Not every expression may always bring back a result value; the most beaten up example of such a situation is a number divided by zero. Another typical example would be invalid argument value that does not allow to return result. In such cases instead of returning the result an exception is to be thrown.
The use of an external value freezes it in the function definition, creating a so called closure.
Expressions and their constituents carry unique types that can be inferred by the F# compiler. As a rule, no implicit type conversions have place ever. F# compiler type checks programs and catches errors that may otherwise occur at run-time for dynamically typed languages.
Type assignment algorithm performed by the compiler often allows the programmer to omit type declarations if the context unambiguously determines them. It finds most general type possible for value binding and expression evaluation.
A function may allow a generic type of argument(s); for example, the implementation of a function calculating the sum of the list elements of int
, or int64
, or bigint
may be the same.
Inherited data structures include the following:
Pattern matching is a powerful mechanism of data structure decomposition, allowing you to disassemble the data aggregate into components or define the processing depending on a particular structure/attributes of the data aggregate.
Data references were introduced in ML in order to support mutable storage and, more broadly, imperative programming. F# inherits this feature for backward compatibility with ML without reservations. Values of the ref
type allow you to achieve mutability, changing states and representing global variables.
F# follows OCaml in this respect, so recursive function binding should be labeled with the rec
attribute. let rec
immediately puts the name of the function into the scope shadowing the potential duplicate from the outer scope. In absence of rec
attribute let
puts the name of the function into the scope only after the body is fully defined, making the reference either unavailable within the function body, or in the worst case using an unintentionally shadowed outer one in place of the intended.