Let me = „introduce F#”

data: 9 lipca, 2014
czas czytania: 19 min
autor: Konrad Bałys

If you wonder which programming language is the best I unfortunately have to disappoint you. There is no such thing as „the best programming language” or „the most powerful one”. Additionally, there is also no universal language which could satisfy all needs and be efficient in literally every field. Universal languages, such as very popular C#/Java, could address most, if not all, common programming tasks. However, those languages could be easily outdone by other dedicated languages to solve particular issues. It is beneficial to know which programming language to choose to write better, bug less and more efficient software. This is just another reason why you should learn a new language.

In this article I would like to present some basic concepts, which lay behind F#, a language with clean syntax, powerful multi-threading capabilities and a great interoperability with Microsoft .NET.

A world of functional programming

You might have probably heard about „functional languages”. This term is mostly used in academia therefore, some basic concepts should be repeated.

What is functional programming? For most programmers the easiest definition is that it is a style of programming, just the opposite of the imperative programming – the way most of us program every day. Probably, the most noticeable difference between those two mentioned programming styles is how data is handled. In imperative style variables are mutable whereas in functional programming everything is immutable. There are, however, programming languages which are purely-functional (like Haskell), and others, which follow functional style but allow you to modify variables on demand (like F# and OCaml). Immutable nature of functional programming could be easily depicted by the following two code examples (code 1 and code 2).

Code 1. Imperative statements in C#

int counter = 0;
counter = counter + 5;

Code 2. Functional statements in C#

const int counter = 0;
const int result = counter + 5;

The obvious difference is that in functional programming „variables” don’t change their values once they are defined (there are immutable = constants). A new value is assigned to another constant, not modified in place. In an imperative programming there is a concept of variables whereas in functional – there are only values.

When it comes to functions, in imperative programming their output depends not only on function parameters (if any) but also on the state of a program. On the other hand, in functional programming, a function depends only on the input parameters thus function could be called many times and it will always return the same value provided that parameters are the same.

Why is it so important? Mutable values and stateful functions introduce complex time-dependent interactions into your code, especially in concurrent/parallel programs – this could be the root cause of a lot of nasty bugs. Anyone who has ever written a concurrent/parallel program with shared state is aware what this is all about. Also, functions without side-effects are easier to test and maintain because they rely merely on their input arguments.

There is a saying, which I think, quite nice summarizes functional programming:

Functional programming is like describing your problem to a mathematician. Imperative programming is like giving instructions to an idiot.

F# description

So what actually is F#? In a nutshell, F# is a multiparadigm programming language built on .NET, designed to support different programming approaches, including functional programing. Brief summary could look as follows:

  • F# supports functional programming – it is more important what the program should do than how should work
  • F# is statically typed therefore F# code is type-safe – the type information is known at compile time
  • F# is a part of .NET and runs on the CLI so it supports .NET concepts natively
  • F# supports imperative programming
  • F# supports object-oriented programming

Hello World

To run the first Hello World application you need F# compiler (fsc.exe) which is a part of Visual Studio since the 2010 version. Outside of Visual Studio and on non-Microsoft platforms, you can still write and deploy F# applications using the open source Mono platform. Here is the simplest code for the Hello World (code 3).

Code 3. Hello World in F# (HelloWorld.fs)

// Hello, World! - HelloWorld.fs
printfn "Hello, World!";

After creating a text file with above presented content just run the following command (code 4). After a compilation and running HelloWorld.exe produces the following output.

Code 4. F# command-line compilation

c:\\fsharp>"c:\\Program Files (x86)\\Microsoft SDKs\\F#\\3.0\\Framework\\v4.0\\fsc.exe";
HelloWorld.fs
Microsoft (R) F# Compiler version 11.0.60610.1
Copyright (c) Microsoft Corporation. All Rights Reserved.
c:\\fsharp>HelloWorld.exe
Hello, World!

Before running this example in Visual Studio it is worth trying to run it in a browser using a great online F# compiler http://www.tryfsharp.org/Create. The output is, not surprisingly, the same.

F# Interactive (FSI)

Visual Studio comes with a useful tool called „F# Interactive”, which interprets F# interactively. It can be considered as a more powerful Interactive Window known from Visual Studio, which also works when IDE is not in debug mode. This tool is known as REPL, which stands for read, evaluate, print and loop. Once the FSI window is available, it accepts F# code until you terminate the input with double semicolon (;;) and a newline. It compiles and executes received code, then prints the results. If the FSI window evaluates an expression that was not assigned to value, it will assign it to the name “it” by default (code 5).

Code 5. Evaluation of a value not assigned to a name

val it : int = 1

To execute the code in F# Interactive, highlight a section of code and press Alt-Enter or right click and select „Execute in Interactive”. You can open the F# Interactive Window from the „View” menu.

If you compile F# code into an executable (an application) you will be able to see the output only if you use I/O functions e.g. printfn. FSI allows you to see more – whenever expression is evaluated it is displayed on the output. Command line version of FSI is available at the following location (code 6).

Code 6. F# Interactive console

c:\\Program Files (x86)\\Microsoft SDKs\\F#\\3.0\\Framework\\v4.0\\fsi.exe

The most useful two commands are presented in code 7 (use them and you will not get stuck in interpreter as in Vim editor).

Code 7. F# Interactive two useful commands

#help;;                Display help
#quit;;                Exit

F# and Visual Studio

Finally, we can move on to Visual Studio. To begin with, we have to create an empty F# application as in the following figure 1.

Figure 1. New F# application

Figure 1. New F# application

If we paste the same content as previously into the *.fs file and then hit CTRL + F5 (Run without Debugger) we will receive the same “Hello, World!” output but this time in console application.

Visual Studio Project structure

F# source files stored in Visual Studio are compiled in the same order as they appear in Solution Explorer. You cannot call functions and classes which are not defined earlier in the code file or other files compiled before the file where the function or class is used. This is a unique characteristic of F#. The compilation order could be easily changed by right clicking on a code file and selecting „Move Up” or „Move Down”.

F# Syntax

It is high time to focus on language syntax. Code 8 is the equivalent of the code presented in the beginning of the article (code 2). If you run it in F# interactive you will receive the following output.

Code 8. Functional statements in F#

let counter = 0
let result = counter + 1;;
// ==================== output ====================
val counter : int = 0
val result : int = 1

The let keyword creates an immutable binding between an identifier and value or expression. It doesn’t declare mutable variable but rather constant. The output prints two lines (val) along with the types (: int) and numeric values. F# is a statically typed language (just like C#) with a very sophisticated type inference system that allows you to avoid specifying types everywhere. F# is case sensitive so two statements presented below are different in the F# world.

Code 9. Two different name bindings

let number = 1
let Number = 2;;

F# with a default lightweight syntax forces strict formatting rules. For instance, whitespaces (spaces and newlines) are significant because semicolons and curly braces are not used. Indentation is very important and strictly defined. Omitting semicolons and curly braces makes the code easier to read and understand but it takes some time to get used to the missing „;”.

In the following example (code 10) if, elif and else must be on the same indentation level to be matched. Sections with elif and else are optional. The part of code which should be executed is inferred using indentation – each statement between if/elif/else must have higher indentation level to be executed. Similarly, the body of functions must be indented to execute properly. To sum up, tab signs (’\t’) are not used in F# and you should always use spaces and indent your code carefully.

Code 10. Conditional statement. Elif and else parts are optional. Code between elif and else will execute

let x = 2
if x = 1  then 
    printfn "One";
elif x = 2 then
    printfn "Two";
    printfn "Two";
else 
    printfn "Three";
    printfn "Three";
    printfn "Three";;

The following example presents three types of comments available in F# (code 11).

Code 11. A sample with different comments

// single line comment
(* multiline 
...
comment *)
/// Visual Studio documentation comment

If you are interested in what the equivalent of assignment operator is, in F# it is „<-„. To use this operator you have to declare mutable variable. Fortunately, mutable variables are also supported by F#. This is not something which you would normally do in functional programming but code 12 serves as an example.

Code 12. Imperative statements in F#

let mutable counter = 0
counter

Assignment operator is mostly used in the .NET interoperability. Speaking of interoperability, in F# it is possible to call .NET libraries. The following code prints out current date and time – see code 13.

Code 13. Calling .NET classes

open System
let currentDateTime = DateTime.Now.ToString()
printfn "%s" currentDateTime;;

Keyword Open includes System namespace – it is an equivalent of the C#’s import. Mutable variables and inclusion of .NET namespaces are key features required for .NET interoperability. F# can take advantage of any .NET library natively by calling directly into it. The opposite is also true – any code written in F# can be consumed by other .NET languages. Unfortunately, sometimes seamless interoperability is not possible and some type conversion is required beforehand.

F# Arithmetic

F# supports the following arithmetic operators: +, -, *, /, % and **. The last one is the equivalent of power function and the following code (code 14) returns 256.0 and 1024 respectively. To compute the power of an integer use pown function.

Code 14. Power operator

let floatResult = 2.0 ** 8.0
let intResult = pown 2L 10;;

By default, all arithmetic operators do not check for overflow and there are no implicit conversions (compiler will not automatically convert primitive data types like int16 to int32).
The list of bitwise operators: &&& (AND), ||| (OR), ^^^ (exclusive OR), ~~~ (bitwise negation), <<< (shift left), >>> (shift right). And finally, the Boolean operators are as follows: && (AND), || (OR), and not. All of them are evaluated using short circuit evaluation, which means that conditional expression is evaluated until it is known for sure and then the rest is omitted. When it comes to equality, the following operators are available: = (equals), <> (not equal to), <, <=, >, >= and Compare (returns -1/0/1).

Functions

So far we have learned how to declare simple values and use basic operators. Now it is high time to move to something a bit more useful – a function declaration.

Functions can be used as parameters or assigned to a value or variable just like an integer or a string. In C# terms, it’s very similar to the delegates and lambda expressions – see code 15.

Code 15. Increase function

let inc x = x + 1
// let inc (x) = x + 1
inc 7;;
// ==================== output ====================
val inc : x:int -> int
val it : int = 8

Functions are also defined using the same let keyword as for defining values. Everything after the function name separated by whitespace is treated as parameters. Parentheses are not used if function receives at least one parameter. There is no return keyword used to return value, just the last instruction serves as function value. After you run code 15 you will receive output as in lines 5 and 6. The first line in output states that we have declared function which transforms integer into integer (int -> int) and the second one presents the returned value (which is unnamed – therefore it is assigned to it).

F# equivalent for void value (= no value) is unit represented in code by empty parentheses (). Function could take no parameters and such value is described by unit. Code 16 presents functions with different number of parameters.

Code 16. Functions with different number of parameters

open System
let currentDateTime() = DateTime.Now.ToString()
let multiply x y = x * y
let result = currentDateTime()
multiply 2 8;;
// ==================== output ====================
val currentDateTime : unit -> string
val multiply : x:int -> y:int -> int
val result : string = "2014-06-26 22:03:43";
val it : int = 16

First line in output defines that currentDateTime is a parameter less function returning string. Second line means precisely a function taking integer which returns a function that takes and returns an integer. The following lines are just calculated value. This is important to remember that above construction is a convolution of two functions rather than one function taking two parameters. Please note that function types were automatically inferred by a compiler. If some particular type is required it is possible to define type parameters explicitly (code 17).

Code 17. Function with two parameters (float)

let multiply (x:float) (y:float) = x * y
multiply 2.0 8.0;;

In functional languages, functions are called to yield a result. But not only function returns value in F#. This is also true for conditional statements. Code 18 will return „odd„. Please note the nesting – the body of isEven is indented and this is also true for the code in if statement.

Code 18. Conditional expression returning a value

let isEven x = 
    if x % 2 = 0 then
        "even";
    else
        "odd";
isEven 5;;
// ==================== output ====================
val isEven : x:int -> string
val it : string = "odd";

Other types of functions are recursive functions. They are declared similarly as normal functions also using let keyword but this time rec must be added to a function declaration after let to specify that a function is recursive. Recursive functions are very important in functional programming (see code 19). They are heavily used when replacing mutable locals and loops (with recursion), which usually produces shorter and easier to reuse code with no mutable variables.

Code 19. Recursive function which calculates factorial of an integer

let rec factorial n = 
    if n = 0 then 
        1 
    else 
        n * factorial (n-1)
let result = factorial 5;;
// ==================== output ====================
val factorial : n:int -> int
val result : int = 120

Tuples

F# supports many different types. So far, I have mentioned unit (no value – no type) and primitives (int/float). There is also „’a” – a generic type (defined with apostrophe), 'a -> 'b – function type and „’a option” – an option. Here I would like to briefly describe tuples, arrays and lists.
If you want to carry multiple values at once consider using Tuples. This type bears a strong resemblance to Tuple<T1, T2…> type in C#. It is defined as „’a * 'b” and in code it is a pair of integers: int * int. Tuple elements are accessed mostly by pattern matching but in the example you can find another approach, which uses function fst and snd returning first and second elements respectively. Let keyword can also serve as tuple’s items accessor (code 20).

Code 20. Tuples

let tuple = (1, 2)
let first = fst(tuple)
let second = snd(tuple)
let x, y = tuple;;
// ==================== output ====================
val tuple : int * int = (1, 2)
val first : int = 1
val second : int = 2
val y : int = 2
val x : int = 1

Lists and Arrays

Arrays allows to link data together to form a static chain. Although number of items in the array collection cannot change after declaration each collection’s item itself can be amended by

Code 21. Arrays and standard processing

let array1 = [| |]                        // empty array   
let array2 = [| "one"; "two"; "three" |]
let array3 = [| 1 .. 10 |]
array3.[0]

Lists are similar concept to arrays, which allow to link data together to form a chain. The main difference between arrays and lists is that number of items in lists can be changed whereas the items cannot– this is exactly the opposite as for arrays. Lists support two basic operators, which are used to join lists. Cons operator (::) adds element to the head of the list while @ operator joins two lists together. On the list (and also on arrays) you can call a great number of functions and aggregates. I will only list them, complete description can be found in language specification (see references):length, head, tail, exists, rev, tryFind, zip, filter, partition, map, fold, reduce, iter. Each of these functions is called List.function parameters or Array.function parameters, where the function can be replaced with function names specified earlier. A type of list is „’a list”, where „’a” is replaced with a concrete type. Code 22 contains some example code related to lists.

Code 22. Lists and common statements

let list1 = [ ]              // empty list            
let list2 = [ 1; 2; 3 ]    
let list3 = 0 :: list2       // add 0 to the head    
let list4 = [ 1 .. 5 ]  
let list5 = list4 @ list3;;  // join two lists
// ==================== output ====================
val list1 : 'a list
val list2 : int list = [1; 2; 3]
val list3 : int list = [0; 1; 2; 3]
val list4 : int list = [1; 2; 3; 4; 5]
val list5 : int list = [1; 2; 3; 4; 5; 0; 1; 2; 3]

Please note that list supports adding new values to the collection – third and fifth lines in code 22. The following sample performs some basic tasks on the list of integers (code 23).

Code 23. List of integers and their processing

let numbers = [ 0 .. 5 ]
let square x = x * x
let result = List.map square numbers
let sum = result |> List.sum
// or more concise 
let sum2 = numbers |> List.map square |> List.sum
let sum3 = numbers |> List.map (fun x -> x * x) |> List.sum;;
// ==================== output ====================
val square : x:int -> int
val result : int list = [0; 1; 4; 9; 16; 25]
val sum : int = 55
val sum2 : int = 55
val sum3 : int = 55

Pay close attention to the pipeline operator|>. This is very important operator in F# which allows you to pipeline operations from left to right. As it is presented on code 24 this operator replaces the order of operands. As a result, the aggregate which calculates the squares in sum2 is fed with numbers list and later the result calculated is passed to sum aggregate. Basically, this operator applies parameters to functions.

Code 24. Forward pipeline operator

let (|>) x f = f x

F# supports two concepts – Pipelining (|> and <| operators, mentioned above) and Function Composition (>> and << operators). Function Composition operator creates a new function which is a composition of other existing functions. The result is not calculated until composed function is called. Please see [1] for more details. Code 23 also uses lambda – this is a nameless function declaration but with a fun keyword instead of a name (code 25).

Code 25. Lambda expression

fun x -> x * x

To use lambda expression with list operator you have to take it into parentheses. Functions in F# can be composed from other functions. The composition of two functions function1 and function2 is another function that represents the application of function1 followed by the application of function2. The most important fact for now is that there are three main reasons to use |> :

  • Type inference – type information could flow from input objects to the functions manipulating those objects, which helps F# to resolve some language constructs
  • You can pass an intermediate results to the next function
  • Code is more concise

There is an extensive list of List.[function] and Array.[Function] to operate on List and Arrays. Please see documentation for details [1].

Unit of Measure

The feature I highly appreciate in F# is Units of Measure. It is possible to associate integers and floats with units such as length, mass, time etc. Then, F# compiler is able to verify whether arithmetic relationships have the correct units, which helps to prevent errors.

The measures types have to be declared using <Measure> attribute. Code 26 is an example in which basic math calculations succeed only if compatible units are processed together. It is impossible to add inches to centimetres because an error is raised. After conversion (a call to method toInches) the code completes successfully.

Code 26. Units of Measure

// [] type unit-name [ = measure ]
[] type cm
[] type inch
let cmPerInch = 2.54<cm/inch>
let toInches (x : float) = x / cmPerInch
let fourInches = 4.0
let fiveCentimetres = 5.0
let result1 = fourInches * 2.0
// error: The unit of measure 'cm' does not match the unit of measure 'inch'
// let error = fourInches + fiveCentimetres;;
let result2 = fourInches + toInches(fiveCentimetres);;
// ==================== output ====================
[]
type cm
[]
type inch
val cmPerInch : float<cm/inch> = 2.54
val toInches : x:float -> float
val fourInches : float = 4.0
val fiveCentimetres: float = 5.0
val result1 : float = 8.0
val result2 : float = 5.968503937

A unit library is available in the F# PowerPack. This library includes SI units and physical constants.

Summary

F# is a very powerful and featured language so it seems to be a hopeless task to cover many language programming aspects in just one article. I hope I will have a chance to focus on the other interesting features such as collection operators and processing, complex data structures (classes), integration with .NET and type providers in the future.

It is good to learn. It is even better to learn new programming language. You can start right now with www.tryfsharp.org. I also strongly encourage you to focus on functional programming even in normal working tasks – try to use lambda and LINQ expressions as much as possible. I hope that after reading this article you understand the title now :).

Maybe in the future you will consider moving part of your program to F#?

References

[1] http://msdn.microsoft.com/en-us/library/dd233154.aspx – Visual F# documentation
[2] Try FSharp http://www.tryfsharp.org/ – Learning F# – comprehensive online portal for F# with tutorial and good looking chart library
[3] Programming F# 3.0, Second Edition, by Chris Smith. Copyright 2012 Chris Smith, 978-1-449-32029-4.”
[4] An Introduction to Functional Programming for .NET Developers (June 2014) http://msdn.microsoft.com/en-us/magazine/ee336127.aspx

Newsletter IT leaks

Dzielimy się inspiracjami i nowinkami z branży IT. Szanujemy Twój czas - obiecujemy nie spamować i wysyłać wiadomości raz na dwa miesiące.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.