Header menu logo Teaching

BinderScriptNotebook

Match Expressions

There is a very good discussion of match expressions with examples on FSharp For Fun and Profit.The F# Language reference and Tour of F# are also good resources on this.

The idea is that you give the match expression a value to match, the things it can match to, and then what you want to return for each of those matches.

Simple match expressions such as the one below are similar to if/then/else statements. However, as shown in the linked F# for fun and profit article, pattern matching is can do much more than if/then/else.

let f1 x =
    match x with // x is the thing that I am matching
    | 1.0 -> 1.0 // when x matches 1.0 then return 1.0
    | 2.0 -> 1.0 // when x matches with 2.0 then return 1.0
    | 3.0 -> 7.0 + 7.0 // when x matches with 3.0 return 7.0+7.0
    | z -> z ** 2.0 // everything else matches an arbitrary y, and let's return y**2.0
    
[ 1.0 .. 10.0] |> List.map f1

Question 1

Write a function named ma that takes x: float Option as an input. Use a match expression to output the float if x is something and 0.0 if the float is nothing. Provide a test case for both cases to show that the function works.

answer

let ma x = 
    match x with
    | None -> 0.0
    | Some y -> y

let ma2Some7 = ma (Some 7.0) // returns 7.0
let ma2None = ma None // returns 0.0
val ma: x: float option -> float
val ma2Some7: float = 7.0
val ma2None: float = 0.0

or, see the x in the (Some x) part of the match expression is the float, not the original (x: float Option) To see this, hover your cursor over the first two xs. it says x is float Option. Then hover over the second two xs. It says x is float. Two different xs!

let ma2 x = 
    match x with
    | None -> 0.0
    | Some x -> x

let ma2Some7Other = ma2 (Some 7.0) // returns 7.0
let ma2NoneOther = ma2 None // returns 0.0
val ma2: x: float option -> float
val ma2Some7Other: float = 7.0
val ma2NoneOther: float = 0.0

Question 2

Write a function named mb that takes x: float as an input. Use a match expression to output 1.0 if x is 1.0, 4.0 if x is 2.0, and x^3.0 if x is anything else. Provide 3 tests for the 3 test cases to show that the function works.

answer

let mb x = 
    match x with 
    | 1.0 -> 1.0
    | 2.0 -> 4.0
    | x -> x**3.0

let mb1 = mb 1.0 // evaluates to 1.0
let mb2 = mb 2.0 // evaluates to 4.0
let mb7 = mb 7.0 // evaluates to 343.00
val mb: x: float -> float
val mb1: float = 1.0
val mb2: float = 4.0
val mb7: float = 343.0

Question 3

Write a function named mc that takes a tuple pair of ints x: int * int as an input. Handle these cases in the following order:

  1. if the first int is 7, return "a".
  2. if the second int is 7, return "b".
  3. For everything else, return "c".

Finally, test the function on (7,6), (6,7), (7, 7), and (6,6). Make sure that you understand how those 4 examples are handled.

answer

let mc x =
    match x with
    | (7, _) -> "a" // the _ in (7, _) indicates wildcard; it matches anything.
    | (_, 7) -> "b" 
    | _ -> "c" // wild card at the end catches anything remaining.

let mc76 = mc (7,6) // evaluates to "a" because it matches the first case and stops checking.
let mc67 = mc (6,7) // evaluates to "b" because it matches the second case and stops checking.
let mc77 = mc (7,7) // evaluates to "a" because it matches the first case and stops checking.
let mc66 = mc (6,6) // evaluates to "c" because it matches the last wildcard.
val mc: int * int -> string
val mc76: string = "a"
val mc67: string = "b"
val mc77: string = "a"
val mc66: string = "c"

Question 4

Consider this list of trading days for a stock and it's price and dividends:

type StockDays2 = { Day : int; Price : decimal; Dividend : decimal Option }
let stockDays2 = 
    [ for day = 0 to 5 do 
        let dividend = if day % 2 = 0 then None else Some 1m
        { Day = day
          Price = 100m + decimal day
          Dividend = dividend } ]   
  1. create a new list called daysWithDividends that is a filtered version of stockDays that only contains days with dividends. For each day with a dividend, you should return a (int * decimal) tuple where the int is the day and the decimal is the dividend. Thus the result is an (int * decimal) list.
  2. Then create a list called daysWithoutDividends that is a filtered version of stockDays that only contains days that do not have dividends. For each day without a dividend, you should return the day as an int. Thus the result is an int list.

answer

Days With Dividends:

let daysWithDividends1 =
    // using filter and then a map
    stockDays2
    |> List.filter (fun day -> day.Dividend.IsSome)
    |> List.map(fun day ->
        match day.Dividend with
        | None -> failwith "shouldn't happen because I filtered on IsSome"
        | Some div -> day.Day, div)
val daysWithDividends1: (int * decimal) list = [(1, 1M); (3, 1M); (5, 1M)]

or

let daysWithDividends2 =
    // using choose, this is better. Think of choose
    // as a filter on IsSome and a map combined. Choose applies
    // a function that returns an option. If the
    // option result is Some x then choose returns x.
    // If the result is None then choose filters it out.
    // Notice that we don't have to worry about 
    // the "this shouldn't happen" exception
    // because it literally cannot happen in this version.
    // This is an example of making illegal states unrepresentable.
    stockDays2
    |> List.choose (fun day -> 
        // our function takes a day as an input and outputs
        // a `(int * decimal) option`. That is,
        // an optional tuple.
        match day.Dividend with 
        | None -> None
        | Some div -> Some (day.Day, div))
val daysWithDividends2: (int * decimal) list = [(1, 1M); (3, 1M); (5, 1M)]

Days Without Dividends:

let daysWithoutDividends =
    stockDays2
    |> List.choose(fun day -> 
        match day.Dividend with
        | None -> Some day.Day
        | Some div -> None)
val daysWithoutDividends: int list = [0; 2; 4]

val f1: x: float -> float
val x: float
val z: float
Multiple items
module List from Microsoft.FSharp.Collections

--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
val map: mapping: ('T -> 'U) -> list: 'T list -> 'U list
val ma: x: float option -> float
val x: float option
union case Option.None: Option<'T>
union case Option.Some: Value: 'T -> Option<'T>
val y: float
val ma2Some7: float
val ma2None: float
val ma2: x: float option -> float
val ma2Some7Other: float
val ma2NoneOther: float
val mb: x: float -> float
val mb1: float
val mb2: float
val mb7: float
val mc: int * int -> string
val x: int * int
val mc76: string
val mc67: string
val mc77: string
val mc66: string
type StockDays2 = { Day: int Price: decimal Dividend: Option<decimal> }
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
Multiple items
val decimal: value: 'T -> decimal (requires member op_Explicit)

--------------------
type decimal = System.Decimal

--------------------
type decimal<'Measure> = decimal
module Option from Microsoft.FSharp.Core
val stockDays2: StockDays2 list
val day: int
val dividend: decimal option
val daysWithDividends1: (int * decimal) list
val filter: predicate: ('T -> bool) -> list: 'T list -> 'T list
val day: StockDays2
StockDays2.Dividend: Option<decimal>
property Option.IsSome: bool with get
val failwith: message: string -> 'T
val div: decimal
StockDays2.Day: int
val daysWithDividends2: (int * decimal) list
val choose: chooser: ('T -> 'U option) -> list: 'T list -> 'U list
val daysWithoutDividends: int list

Type something to start searching.