F Sharp Programming/Control Flow
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!