Key features of F#
The following are some points that Distinguish the F# language from other .NET languages:
- F# is a functional first language, which means that functions are treated as first-class citizens, but it also provides ways to work with other paradigms, such as object-oriented programming (OOP) (as in C#).
- Unlike other languages, such as C#, which mixes expressions (language constructs returning a value) and statements (constructs that don't return a value), F# is an expression-based language. You can think of every syntax construct in F# as a small function.
- F# is a strongly-typed language, meaning that the type of every expression in the program is determined at compile time. This allows the compiler to make verifications in our code and enables great tooling support, such as autocompletion, refactoring, and so on.
- Additionally, F# has a very strong type inference mechanism to infer types for the expressions in a program. This removes much of the verbosity usually associated with strongly-typed languages.
- The .NET generics' type system is baked into the core of F#. For example, the programmer doesn't have to specify the functions to be generic; if the F# type system infers the variables can be generic (provided it is implemented that way), the function becomes generic. This makes it easier to write polymorphic code, that is, functions that can be reused with different types.
- F# has a module system that allows data structures to be specified and defined abstractly. Unlike C# namespaces, F# modules can contain functions that help you separate data (types) from logic (functions in modules).
- F# implements a pattern matching mechanism, which allows controlling conditions based upon structural similarities; whereas, other languages only allow value matching as in
IF...ELSE
statements in C#.
Functional and imperative languages
Imperative languages usually modify the state of a variable for most operations. This makes it more difficult to reason about our program, particularly when different parts of our code change values that are globally accessible. When a piece of code modifies a value outside its scope, we talk about side-effects (this may also include other state modifications, such as file or console operations). OOP tries to tame side-effects by encapsulating state. However, this is not always a complete solution, as objects often develop tight and complex dependencies with each other that are still difficult to reason with.
Functional languages solve this problem using pure functions. Pure functions are closer to the mathematical ideal of functions, in the sense that they don't have side-effects (don't change state outside their scope) and always produce the same output given the same input. Pure functions are easier to refactor and reason with because their output is predictable, and can be used as building blocks to write large programs with different techniques of function composition.
F#, as described, is a functional-first language, but the language can also deal with unavoidable side-effects such as file or logging operations.
To compare F# with a more imperative language, we can take the example of a Fibonacci sequence generator, as follows:
public static int Fibonacci(int n) { int a = 0; int b = 1; // In N steps compute Fibonacci sequence iteratively. for (int i = 0; i < n; i++) { int temp = a; a = b; b = temp + b; } return a; }
|
let rec fib n = if n < 2 then 1 else fib (n - 2) + fib (n - 1)
|
Note
For illustration purposes, C# in procedural style is used. It is also capable of more functional implementations, such as Language Integrated Query (LINQ). Also, performance is not taken into consideration.
In an imperative language, the algorithm is normally implemented as a loop, and progress is made by modifying the state of the variables used in the loop.
In F#, the Fibonacci sequence is implemented using recursion. The let
keyword, which defines the function, and the rec
keyword, which specifies the function, can be called recursively. Using recursion, we are parameterizing the state (we will pass the updated values as parameters to the next call) so we do not need to use mutable variables.
However, please note that programs exclusively using a functional style can have performance problems. In this book, we will take an intermediate approach of using imperative code when necessary.