Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Mastering F#

You're reading from   Mastering F# A comprehensive and in-depth guide to writing functional programs using F#

Arrow left icon
Product type Paperback
Published in Nov 2016
Publisher
ISBN-13 9781784393434
Length 264 pages
Edition 1st Edition
Languages
Arrow right icon
Authors (2):
Arrow left icon
Suhaib Fahad Suhaib Fahad
Author Profile Icon Suhaib Fahad
Suhaib Fahad
Alfonso García-Caro Núñez Alfonso García-Caro Núñez
Author Profile Icon Alfonso García-Caro Núñez
Alfonso García-Caro Núñez
Arrow right icon
View More author details
Toc

Basic values

In F#, every valid value must have a type, and a value of one type may not be bound to a value of another type. We will declare values in F# using the let keyword. For example, refer to the following piece of code:

    // variable expression 
    let x = 10 
    // function expression 
    let add a b = a + b 

As you learn F#, you will initially spend a lot of time getting the F# type checker to accept your programs. Being patient and analyzing the results with the F# type checker eventually helps you program better; you will later realize that the type checker is your best friend. Some rules about type checking are as follows:

  • Every expression has exactly one type
  • When an expression is evaluated, one of the following two things can happen:
    • It could evaluate to a value of the same type as the expression
    • It may raise an exception or error (this is actually a side-effect)

The let bindings can also be nested as follows:

    let z = 
        let x = 3 
        let y = 4 
        x + y 

Note

Note that the inner let bindings are placed to the right of the outer let bindings. This is important because the F# compiler determines scope by indentation.

When an expression is bound to a value in a let binding, the value can be used within the body of let (its scope). If a value with the same name was declared previously, the previous definition is overridden with the new definition; this is called shadowing and is often used in F# instead of value mutation. Let's consider the following example:

    let test() = 
        let x = 5 
        do 
            let x = 10 
            printfn "%i" x // prints 10 
        x // returns 5 

Here, we are not reassigning 10 to the previously declared x value. Instead, we are effectively creating a new value, as you can see, because in the outer scope x is still 5.

If needed, it is still possible to modify values in F#. For that, we will need to use the mutable keyword, as shown in the following piece of code:

    let test() = 
        let mutable x = 5 
        do 
            x <- 10 // assignment 
            printfn "%i" x // prints 10 
        x // returns 10 

Getting started with functions

Anonymous functions, or lambdas, are defined with the fun keyword, followed by a sequence of parameters with the -> separator, and then the body of the function expression. The following is an example function to add two numbers:

> let sum = fun a b -> a + b;; 
val sum : a:int -> b:int -> int 

Note

A shortcut for the preceding code is let sum a b = a + b.

The type for sum is int -> int -> int. Like other functional languages, this means that arguments are curried. Think of sum as a function returning another function, which can be partially applied, as we will see in the following section.

Partially applied functions

In F#, and other similar functional languages, functions actually have only one input and output. When we declare a function with multiple arguments, we are actually building functions that return other functions until the desired output is obtained. In the following code snippet, the two functions are effectively identical:

> let sum = fun x y z -> x + y + z;; 
val sum : x:int -> y:int -> z:int -> int  
> let sum' = fun x -> fun y -> fun z -> x + y + z;; 
val sum' : x:int -> y:int -> z:int -> int  

Note

The apostrophe is a valid character in F# and it is often used to mark values with a slight modification from a previously existing one.

The application of lesser arguments than the arity (the total number of arguments a function can accept) is called a partial application.

> let sum a b = a + b;; 
> let incr_by_ten = sum 10;; 
> incr_by_ten 5;; 
val it : int = 15 

Partial functions also help in writing concise code with F# pipeline operators, as shown in the following code:

    let res1 = List.map (fun x -> x + x) [2; 4; 6] 
    let res2 = List.filter (fun x -> x > 5) res1 

The preceding code can be rewritten in a more expressive way using the pipe (|>) operator:

    [2; 4; 6] |> List.map (fun x -> x + x) |> List.filter (fun x -> x > 5) 

Note

In F#, infix operators can be declared the same way as functions; the only difference is that we will surround them with parentheses in the declaration (for example, let (++) x y = (x + y) * 2) and then use them in the middle of the arguments (for example, 5 ++ 3).

For C# and VB.NET users, this is much like the continuation style programming with LINQ functions. In LINQ, you will normally pipeline a sequence of function calls, as follows:

    var listOfItems = someItems.Select(x => x.Name).OrderBy(x => x); 

However, for this the type returned by the first method needs to implement the method we want to call next. This is not a limitation when using the pipe operator.

Note

It is also possible to declare functions in a more similar way to languages such as C# (for example, let sum (x, y) = x + y), but there is an important difference-these functions take a single tuple argument. We will discuss tuples in Chapter 2, Functional Core with F#.

Recursive functions

In functional languages, recursion is used to express repetition or looping. For example, the Fibonacci sequence generator can be written as follows:

    let rec fib n = 
        if n < 2 then 1     
        else fib (n - 2) + fib (n - 1) 

We use the rec keyword to define a function as recursive. This is necessary to help the F# type checker infer the types of the function signature.

Every time a function is called recursively, a new routine is added to the call stack. As the call stack is limited, we must be careful not to overflow it. To prevent this, the compiler of F# and most functional programming languages implements an optimization called tail-call, which basically compiles down to a while loop. To enable this optimization, we will need to make sure the recursive call is the last expression in the function.

    // tail-recursion 
    let factorial x = 
        // Keep track of both x and an accumulator value (acc) 
        let rec tailRecursiveFactorial x acc = 
            if x <= 1 then 
                // use the accumulator that has the final result 
                acc 
            else 
                // pass the accumulator + original value again to the recursive method 
                tailRecursiveFactorial (x - 1) (acc * x) 
        tailRecursiveFactorial x 1 

Higher-order functions

As functions are first-class citizens in functional languages, we can pass them as arguments to other functions. When a function takes another function as an argument, we it a higher-order function. Let's consider the following example:

> let apply x f = f x 
val map : x:'a -> f:('a -> 'b) -> 'b 
> let sqr x = x * x 
val sqr : x:int -> int 
> let f = apply 5 sqr;; 
val f : int = 25

The preceding code snippets perform the following functions:

  • The apply function takes a function as a parameter and evaluates the function
  • We will declare a sqr function, which squares an int value
  • We will then call the sqr function through apply

Higher-order functions are very important to write composable and reusable code. Earlier, we saw List.map. Many of the functions in the Collections modules (List, Array, and Seq) accept functions as arguments so we can adapt their behavior to our needs.

You have been reading a chapter from
Mastering F#
Published in: Nov 2016
Publisher:
ISBN-13: 9781784393434
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at R$50/month. Cancel anytime