Chapter 5

Booleans & Conditionals

Main Page

Introduction

Decisions, decisions! To make logical decisions, programming languages need a way to determine the truthfulness of a statement. In other words, they need to be able to determine whether an expression is true or if it is false. This chapter deals with Booleans, the stuff of true and false, and the ways in which your F# programs can employ them to conditionally alter program flow.

Booleans

The F# type bool maps to the .NET System.Boolean data type. It can hold one of two values: true or false.

let t = true
let f = false

Boolean Operators

Boolean logic enables us to logically combine true and false values to perform calculations and make decisions based on the outcome. The Boolean operators are AND, OR and NOT.  Each of these operators is represented in F# as a symbolic operator as shown in Table 5.1 below.

Boolean Operator

F# Operator

 AND

&&

 OR

||

 NOT

not

Table 5.1

The following code snippets demonstrate Boolean values used with logical operators.

let t = true        // true
let f = false       // false
let x = t && f      // false
let y = t || f      // true
let fls = not true  // false

In addition to the logical operators AND, OR and NOT, F# supports logical comparison operators. Your programs can compare for equality, values being less-than, greater-than, etc. other values, etc.  Table 5.2 below summarizes the available logical comparison operators.

 

Logical Comparison Operators

Operator

Notes

Examples

=

 

Equality. This is not an assignment operator. It is used only for comparison.  Note that F# does not use == for comparison, as many languages do.

2 = 2   // true

1 = 2   // false

 

>

 

Greater than

1 > 2   // false

 

<

Less than

1 < 2   // true

 

>=

 

Greater than or equals

1 >= 2  // false

 

<=

 

Less than or equals

1 <= 2  // true

 

<> 

 

Not equal

1 <> 2  // true

2 <> 2  // fals

Table 5.2

Making Decisions

Once you can determine whether an expression is true or false, you can start using Boolean values to make decisions and alter program flow based on these decisions. The following section discusses the various ways that you can use Booleans to make decisions and conditionally execute code.

if…then…else

The if...then...else expression evaluates the truthfulness of a given Boolean expression and executes different branches of code depending on the result. The general form of the if...then…else expression is as follows:

if Boolean-expression then expression1 [ else expression2 ]

Note that in F#, the if...then...else construct is an expression.[1] This means that the if…then…else construct evaluates to a value, which is the value of the last expression. If there is no explicit else branch, the construct evaluates to type unit. Therefore, in the absence of an explicit else clause, the then clause must evaluate to unit. Let’s look at the consequences of this.

let a, b = 100, 200
if a < b then "a < b"   // ERROR – evaluates to string and not unit!

Here, we are telling F# that the expression evaluates to a string; however, this will generate an error. Why? Since there is no explicit else clause, the overall type of the if expression is unit. We are, therefore, trying to return a string where F# expects a unit.

We can solve this problem by specifying an else clause that evaluates to the same type that we want to return from the other parts of the if construction. The following code shows the previous example sans the error:

let a, b = 100, 200
if a < b then "a < b" else "a is not less than b"  // OK, with warning

 Now, because we have an explicit else branch that returns a string, the overall expression is expected to return a string. Given that all paths evaluate to a string, F# allows the construct – with a warning. The warning in Visual Studio is: This expression should have type ‘unit’ but has type string. This warning message is telling us that the result of the expression isn’t being stored anywhere – the result is going to unit or “into the void.” To correct this, we assign the expression to an identifier:

let a, b = 100, 200
let s = if a < b then "a < b" else "a is not less than b"  // OK!

Another way to correct this, or to “shut F# up” would be to tell it that we know we are explicitly ignoring the output, like this:

let a, b = 100, 200

(if a < b then "a < b" else "a is not less than b") |> ignore[2]

Another consequence of the if construct being an expression is that all branches must return the same data type.  For example:

let a, b = 100, 200
let s = if a < b then 1 else "a is not less than b" 
    // ERROR: This expression has type string but is used here with type int

Since the else branch evaluates to a string, the overall if construct must evaluate to a string. This means that all branches must evaluate to string as well. In the example above, we are evaluating the expression to an int where F# expects a string. One more example rounds out the possibilities:

let a, b = 100, 200
if a < b then printfn "a < b"

    // OK. Since we have not defined an else branch, F# expects this
    // if to evaluate to unit. printfn evaluates to unit, satisfying
    // this expectation.

Parenthesis

You can use parenthesis to clarify or group Boolean expressions, and to force evaluation order. The following example illustrates the idea:

let a, b, c = 100, 200, 300
if ((a < b) || (c < b)) && (a < c) then printfn "a is the lowest value"

Parentheses are optional when using them for clarity. They are mandatory for forcing evaluation order.

Multiple Branches

The if…then…else construct can evaluate multiple expressions using the else if or the elif construct. elif is a convenience keyword and is exactly the same as spelling out else if. Let’s look at a few examples:

let name, pin = "bob", 123
let securityMessage =
    if name = "bob" && pin = 123 then "welcome bob!"
    elif name = "sally" && pin = 456 then "welcome sally!"
    else "access denied"

In this example, securityMessage gets bound to the value "welcome bob!".

Remember that in F# whitespace is significant. F# uses whitespace to logically and semantically group multiple lines of code together. Failure to indent or misalign the if…elif…else…then above causes compiler errors.

The following example shows how F# uses indentation to group multiple statements.

let name = "bob"
let s =
    if name="bob" then
        printfn "bob is a palindrome"
        "bob backwards is still bob”
   
else
       
printfn "access denied"
        "who are you?"

This example prints "bob is a palindrome" and evaluates to the string "bob backwards is still bob". The final expression of each block is what the entire block evaluates to.

 

You may be thinking to yourself that F#’s decision-making capabilities are rather rudimentary. You may be asking, “Where’s my switch statement?” Well, it turns out that F# does not support a switch statement. Instead, it supports something much more flexible and powerful called pattern matching expressions. To take advantage of pattern matching expressions, we need a little more F# under our belts, so we’ll wait to explore them in Chapter 11.

What You Need to Know

·         A Boolean (F# bool) is a true or false value.

·         F# supports the Boolean operators AND (&&), OR (||), and NOT (not).

·         You can test values for equality and inequality using comparison operators, e.g., =, <=, <>, etc.

·         You can use parenthesis to group and/or clearly delineate Boolean expressions.

·         The if…then…else construct evaluate to an expression. If you do not specify an explicit else clause, the entire construct is assumed to return unit. If you do specify an else clause, the construct is assumed to evaluate to the else’s return type.

·         When chaining multiple if expressions together, you can use elif as a convenience for “else if”.

·         Whitespace is significant and is used to group multiple expressions together under a given if or else clause.

 



[1] This is different from other .NET languages like C#, where if is a statement.

[2] I know we have not yet covered this construct. It simply pipes the result of the expression to the special function ignore, which accepts anything and simply does nothing with it. We will discuss the pipe symbol |> and the ignore keyword in subsequent chapters.

 

Feedback

We welcome your feedback. If you have comments or questions about this chapter, please feel free to e-mail us at

Keep Reading

Next Chapter...