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 or, see the 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
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.
answerlet 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:
- if the first
int
is7
, return"a"
. - if the second int is
7
, return"b"
. - 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.
answerlet 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 } ]
-
create a new list called
daysWithDividends
that is a filtered version ofstockDays
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
. -
Then create a list called
daysWithoutDividends
that is a filtered version ofstockDays
that only contains days that do not have dividends. For each day without a dividend, you should return the day as anint
. Thus the result is anint list
.
answer Days With Dividends: or Days Without 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)]
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)]
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]
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 int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
val decimal: value: 'T -> decimal (requires member op_Explicit)
--------------------
type decimal = System.Decimal
--------------------
type decimal<'Measure> = decimal