Jump to content

F Sharp Programming/Control Flow

From Wikibooks, open books for an open world
Previous: Mutable Data Index Next: Arrays
F# : Control Flow

In all programming languages, control flow refers to the decisions made in code that affect the order in which statements are executed in an application. F#'s imperative control flow elements are similar to those encountered in other languages.

Imperative Programming in a Nutshell

[edit | edit source]

Most programmers coming from a C#, Java, or C++ background are familiar with an imperative style of programming which uses loops, mutable data, and functions with side-effects in applications. While F# primarily encourages the use of a functional programming style, it has constructs which allow programmers to write code in a more imperative style as well. Imperative programming can be useful in the following situations:

  • Interacting with many objects in the .NET Framework, most of which are inherently imperative.
  • Interacting with components that depend heavily on side-effects, such as GUIs, I/O, and sockets.
  • Scripting and prototyping snippets of code.
  • Initializing complex data structures.
  • Optimizing blocks of code where an imperative version of an algorithm is more efficient than the functional version.

if/then Decisions

[edit | edit source]

F#'s if/then/elif/else construct has already been seen earlier in this book, but to introduce it more formally, the if/then construct has the following syntaxes:

(* simple if *)
if expr then
    expr

(* binary if *)
if expr then
    expr
else
    expr

(* multiple else branches *)
if expr then
    expr
elif expr then
    expr
elif expr then
    expr
...
else
    expr

Like all F# blocks, the scope of an if statement extends to any code indented under it. For example:

open System

let printMessage condition =
    if condition then
        printfn "condition = true: inside the 'if'"
    printfn "outside the 'if' block"

let main() =
    printMessage true
    printfn "--------"
    printMessage false
    Console.ReadKey(true) |> ignore
 
main()

This program prints:

condition = true: inside the 'if'
outside the 'if' block
--------
outside the 'if' block

Working With Conditions

[edit | edit source]

F# has three boolean operators:

Symbol Description Example
&& Logical AND (infix, short-circuited)
true && false (* returns false *)
|| Logical OR (infix, short-circuited)
true || false (* returns true *)
not Logical NOT
not false (* returns true *)

The && and || operators are short-circuited, meaning the CLR will perform the minimum evaluation necessary to determine whether the condition will succeed or fail. For example, if the left side of an && evaluates to false, then there is no need to evaluate the right side; likewise, if the left side of a || evaluates to true, then there is no need to evaluate the right side of the expression.

Here is a demonstration of short-circuiting in F#:

open System

let alwaysTrue() =
    printfn "Always true"
    true
    
let alwaysFalse() =
    printfn "Always false"
    false

let main() =
    let testCases = 
        ["alwaysTrue && alwaysFalse", fun() -> alwaysTrue() && alwaysFalse();
         "alwaysFalse && alwaysTrue", fun() -> alwaysFalse() && alwaysTrue();
         "alwaysTrue || alwaysFalse", fun() -> alwaysTrue() || alwaysFalse();
         "alwaysFalse || alwaysTrue", fun() -> alwaysFalse() || alwaysTrue();]
    
    testCases |> List.iter (fun (msg, res) ->
        printfn "%s: %b" msg (res())
        printfn "-------")
    
    Console.ReadKey(true) |> ignore
 
main()

The alwaysTrue and alwaysFalse methods return true and false respectively, but they also have a side-effect of printing a message to the console whenever the functions are evaluated.

This program outputs the following:

Always true
Always false
alwaysTrue && alwaysFalse: false
-------
Always false
alwaysFalse && alwaysTrue: false
-------
Always true
alwaysTrue || alwaysFalse: true
-------
Always false
Always true
alwaysFalse || alwaysTrue: true
-------

As you can see above, the expression alwaysTrue && alwaysFalse evaluates both sides of the expression. alwaysFalse && alwaysTrue only evaluates the left side of the expression; since the left side returns false, its unnecessary to evaluate the right side.

for Loops Over Ranges

[edit | edit source]

for loops are traditionally used to iterate over a well-defined integer range. The syntax of a for loop is defined as:

for var = start-expr to end-expr do
    ... // loop body

Here's a trivial program which prints out the numbers 1 - 10:

let main() =
    for i = 1 to 10 do
        printfn "i: %i" i
main()

This program outputs:

i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
i: 7
i: 8
i: 9
i: 10

This code takes input from the user to compute an average:

open System

let main() =
    Console.WriteLine("This program averages numbers input by the user.")
    Console.Write("How many numbers do you want to add? ")
    
    let mutable sum = 0
    let numbersToAdd = Console.ReadLine() |> int
    
    for i = 1 to numbersToAdd do
        Console.Write("Input #{0}: ", i)
        let input = Console.ReadLine() |> int
        sum <- sum + input
    
    let average = sum / numbersToAdd
    Console.WriteLine("Average: {0}", average)
        
main()

This program outputs:

This program averages numbers input by the user.
How many numbers do you want to add? 3
Input #1: 100
Input #2: 90
Input #3: 50
Average: 80

for Loops Over Collections and Sequences

[edit | edit source]

Its often convenient to iterate over collections of items using the syntax:

for pattern in expr do
    ... // loop body

For example, we can print out a shopping list in fsi:

> let shoppingList =
    ["Tofu", 2, 1.99;
    "Seitan", 2, 3.99;
    "Tempeh", 3, 2.69;
    "Rice milk", 1, 2.95;];;

val shoppingList : (string * int * float) list

> for (food, quantity, price) in shoppingList do
    printfn "food: %s, quantity: %i, price: %g" food quantity price;;
food: Tofu, quantity: 2, price: 1.99
food: Seitan, quantity: 2, price: 3.99
food: Tempeh, quantity: 3, price: 2.69
food: Rice milk, quantity: 1, price: 2.95

while Loops

[edit | edit source]

As the name suggests, while loops will repeat a block of code indefinitely while a particular condition is true. The syntax of a while loop is defined as follows:

while expr do
    ... // loop body

We use a while loop when we don't know how many times to execute a block of code. For example, lets say we wanted the user to guess a password to a secret area; the user could get the password right on the first try, or the user could try millions of passwords, we just don't know. Here is a short program that requires a user to guess a password correctly in at most 3 attempts:

open System

let main() =
    let password = "monkey"
    let mutable guess = String.Empty
    let mutable attempts = 0
    
    while password <> guess && attempts < 3 do
        Console.Write("What's the password? ")
        attempts <- attempts + 1
        guess <- Console.ReadLine()
        
    if password = guess then
        Console.WriteLine("You got the password right!")
    else
        Console.WriteLine("You didn't guess the password")
        
    Console.ReadKey(true) |> ignore
    
        
main()

This program outputs the following:

What's the password? kitty
What's the password? monkey
You got the password right!
Previous: Mutable Data Index Next: Arrays