A brief F# language primer
Even though this book is not intended to be an absolute beginner's primer to F#, if you are new to F# there are certain language fundamentals you must know in order to maximize your learning from this book. Following is a quick F# refresher on basic language constructs, keywords, and salient syntactical features that you will find useful during the course of reading this book Several of these items, especially those related to data-structures, are discussed in greater detail in the following chapters. You can download all these examples and source code from the book GitHub repository at https://github.com/adnanmasood/Learning-fsharp.
F# is a statically typed language, that is, types of the variables are known at compile time. Like all other static type languages, F# uses a type inference to resolve the variable type. F# comes with standard data types such as byte
, sbyte
, int16
, uint16
, int
, uint
, int64
, uint64
, native int
, unsigned native int
, float
or double
, float32
, decimal
, and bignum
(System.Numerics.BigInteger
). A few simple declarations with appropriate suffixes can be seen as follows:
Similar to standard CLR data types, F# also uses the standard mathematical operators such as . Logical operators are supported along with mathematical functions such as . A detailed F# language reference, including Symbol and Operator Reference, can be found at http://msdn.microsoft.com/en-us/library/dd233228.aspx.
At this time, we would also like to briefly introduce you to one of the highly useful features of F# IDE, the REPL. REPL (Read–Eval–Print Loop) is an interactive language shell to take expression inputs, evaluate, and provide output to the users. REPL allows developers to interact with the language easily and to invoke and test expressions in real-time before writing the entire program. FSI (F Sharp Interactive) is the REPL implementation in F#. You will read more about installing and configuring FSI in Chapter 2, Now Lazily Get Over It, Again. For now you can use the command line version of FSI by invoking it directly in a console:
You can also use the #help;;
directive to list other directives inside FSI.
You will see the let
binding being used quite frequently for declaring variables, functions, and so on. Functions put functions in functional programming and hence, they are ubiquitous. Technically speaking, F# doesn't have any statements, it only has expressions. The following is a simple example of a function:
Instead of explicitly returning a value, F# uses a succinct syntax of returning the value of the expression last evaluated.
Recursive functions are defined using the keyword rec
. Here is a simple implementation of a recursive Fibonacci function:
The preceding code for the Fibonacci method takes one parameter as an input. However, a function can have more than one parameters following the same code.
Type inference in F# is an important construct to remember. For instance, in the case of the multiplication function in the preceding line of code, F# assumes the type inference of the arguments as int
. A hint can be provided to specify the appropriate data type.
Nested or anonymous functions are now commonplace in languages such as C# and Java. These are the special functions that reside inside another function and are not visible from an outside scope. For instance, refer to the following code snippet:
However the preceding function will fail upon execution without a hint. We will see the following error on screen:
But the same method will work just fine if the specified data type is passed as float
.
Moreover, you cannot call the inner function directly. That is why the direct call to the square method will return the following error:
The conditionals are fundamental to any programming language. F# provides a great pattern-matching scheme along with traditional if...else
expressions. The following is a simple if...else
check:
The print expression will return a value. You can also see the use of elif
which is used as a shortcut for else if
.
Tuples are now part of a standard CLR system, but most of us remember the struggle before tuples. Tuples are the containers for potentially different types, as seen in the following code:
Speaking of collections, arrays in F# are mutable, fixed-sized sequences. Arrays are fixed in size and zero-indexed, with the elements encapsulated within [| … |]
and separated by a semi-colon.
The individual elements of the array can be accessed as follows:
This also applies to the strings where you can access an individual element of a string as follows:
Arrays can be created using ranges as follows:
They can be sliced using index (arrays are zero base indexed) as seen in the following code:
Functions in F# can be applied partially; it gets interesting here. A simple add
function can be defined as follows:
We can apply it partially to make it add 10
every time. Therefore, the following statement:
This can be bound as a method name, or a closure to be exact as seen here:
This can be explicitly called like the original method, allowing us to compose complex methods using the basic ones. Here, Add10
is a closure that takes one argument and adds 10
to it as seen in the following code:
Closures are functionally defined as a first-class function with free variables that are bound in the lexical environment. In F#, functions are first class members of the programming society; closures encapsulate an environment for pre-bound variables and create a code block. This way we can pre-define some arguments (in this case, 10
). Closure promotes reuse and helps in building complex functions from simpler ones.
With functions as the first class citizens, in F# we can create higher order functions, that is, functions upon functions. Higher order functions operate by taking a function as an argument, or by returning a function. Following are two simple functions:
Now we will define a higher order function which applies function upon function:
Now we will use the InvokeThrice
function, which will apply the function upon itself three times as defined in the preceding line of code:
In this example, you witnessed the amazing power of declaring functions. A similar approach can be applied to division as follows:
In the preceding syntax for the InvokeThrice
function, you will notice the use of a lambda expression. Lambda expressions are ubiquitous in functional programming. In reality, these expressions are syntactic sugar (directives, shortcuts, or a terse way of defining something) to declare anonymous methods. A lambda expression is created using the fun
keyword, that is, function, followed by arguments which are supposed to be passed to the function. This function declaration is then followed by the lambda arrow operator ->
and the lambda expression which defines the body of the function. For example, instead of passing the function, I can pass the lambda expression during the InvokeThrice
invocation to apply exponential operation (power 3).
Another frequently used F# operator is pipelining |>
, which allows us to push arguments onto functions. For example, check the following cubeMe
method:
The preceding method can also be called as cubeMe 3
or 3 |> cubeMe
.
The results will be the same. The pipelining operator allows us to do chaining such as:
This comes in handy when you build functional composites.
Mapping is a frequently used operation in functional programming. Map applies functions on a collection, and displays output as a new list, based on the result of this function. For arrays, F# provides a built-in operation called map
. The map
operation takes two arguments—a function and an array of elements. For example, refer to the following array of integers:
The following is the mapping function that will square all the elements in the array, and return a new array:
When you run the square method on nums
, you get the following output:
The opposite of the map operation is the fold operation. You can think of the folding operations as aggregations. As seen in the preceding code snippet, map
takes a collection of arrays and generates another collection. However, the folding operation takes a collection of arrays as input and returns a single object.
For example, in the next statement, Array.fold
takes three arguments—a function, an initial value for the accumulator, and an array. It sums up the squares of all the three parameters and returns the output:
Along with map
and fold
, filtering is another operation which comes in handy to select and filter elements based on a condition (predicate). In the following example, Array.filter
takes an array of last names and folders them based on the length. Any last name longer than 6 characters will be classified as a long name.
The output will be as follows:
Similar to map, which applies a function on a collection, a zipping function takes two collections and combines them. In the following example we have two lists:
A zip operation when applied on the array returns their full names:
Last but not the least, another salient feature of F# language is Lazy
or delayed evaluation. These lazy
expressions only get evaluated when forced, or when a value is required to be returned. The value then gets memoized (a fancy functional name for caching), and is returned on future recalls. The following is a simple divide
method:
When you invoke the method with the Lazy
keyword, the output shows that the value does not get created right away.
However, this can be changed by forcing the results by calling answer.Force()
:
Now upon force invocation, you would see the value was evaluated by calling the function and therefore you also see dividing 8
by 2
getting printed on the FSI console. Upon consecutive calls such as
The output would be as follows:
You would not see dividing 8 by 2
getting printed on the FSI console because the value has been computed and memoized. Collections such as sequence are lazy
by default, which you will learn in subsequent chapters.
This concludes our whirlwind introduction to the F# programming language; if you are new to F#, you should revise this section a couple of times and run this in the interactive environment to gain familiarity with these fundamental language constructs.