On Github lasandell / FSharpPatternMatching
f(0) = 1
f(1) = 1 * 1 = 1
f(2) = 2 * 1 * 1 = 2
f(3) = 3 * 2 * 1 * 1 = 6
f(4) = 4 * 3 * 2 * 1 * 1 = 24
f(5) = 5 * 4 * 3 * 2 * 1 * 1 = 120
...
f(0) = 1
f(1) = 1 * f(0) = 1
f(2) = 2 * f(1) = 2
f(3) = 3 * f(2) = 6
f(4) = 4 * f(3) = 24
f(5) = 5 * f(4) = 120
...
f(n) = n * f(n - 1)
1: 2: 3:
let rec fac n =
if n = 0 then 1
else n * fac (n - 1)
1: 2: 3: 4:
let rec fac n =
match n with
| 0 -> 1
| _ -> n * fac (n - 1)
1: 2: 3: 4: 5:
let rec fac n =
match n with
| 0 -> 1
| _ when n > 0 -> n * fac (n - 1)
| _ -> failwith "n must be >= 0"
To understand pattern matching, you must understand the F# type system!
Exception: Type Test Pattern
1: 2:
1, 2 1, "test", "red"
1: 2: 3:
[1; 2; 3] 1::[2; 3]
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
type Person {
LastName: string
FirstName: string
}
let person = {
FirstName = "Joe"
LastName = "Smith"
}
{ person with FirstName = "John" }
1: 2: 3: 4: 5: 6: 7: 8:
type Shape =
| Circle of int
| Square of int
| Rectangle of int * int
type option<'t> =
| Some of 't
| None
pattern type
examples
constant
1.0, "test", Color.Red, ()
variable
x, number
tuple
(1, 2), (x, y)
array
[¦1; 2¦], [¦x; y¦]
list
[1; 2], [x; y]
cons
h::t, x::y::t
union
Some x, Circle(center, radius), Circle(radius = r)
exception
Failure msg
record
{ FirstName = "John", SSN = ssn }
wildcard
_, (x, _)
pattern type
examples
or
(1, x) ¦ (2, x)
and
(x, y) & z
as
(x, y) as z
type test
:? Dog as d
1: 2: 3: 4:
let rec sum list =
match list with
| [] -> 0
| item::items -> item + sum items
1: 2: 3:
let rec sum = function
| [] -> 0
| item::items -> item + sum items
Usage: reverse [options]
-i {file}, --input {file} Specify an input file (stdin by default)
-o {file}, --output {file} Specify an output file (stdout by default)
-l, --reverse-line-order Reverse order of lines in file
-c, --reverse-line-content Reverse content of each line
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
type Settings = {
InputFile: string option
OutputFile: string option
ReverseLineContent: bool
ReverseLineOrder: bool
}
let defaultSettings = {
InputFile = None
OutputFile = None
ReverseLineOrder = false
ReverseLineContent = false
}
[<EntryPoint>]
let main args =
let settings = parseArgs defaltSettings (List.ofArray args)
reverseFile settings
0
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
let rec parseArgs settings args =
match args with
| [] ->
settings
| "--input"::file::rest ->
parseArgs { settings with InputFile = Some file } rest
| "--output"::file::rest ->
parseArgs { settings with OutputFile = Some file } rest
| "--reverse-line-order"::rest ->
parseArgs { settings with ReverseLineOrder = true } rest
| "--reverse-line-content"::rest ->
parseArgs { settings with ReverseLineContent = true } rest
| _ ->
printUsage()
exit 1
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
let rec parseArgs settings args =
match args with
| [] ->
settings
| ("-i"|"--input")::file::rest ->
parseArgs { settings with InputFile = Some file } rest
| ("-o"|"--output")::file::rest ->
parseArgs { settings with OutputFile = Some file } rest
| ("-l"|"--reverse-line-order")::rest ->
parseArgs { settings with ReverseLineOrder = true } rest
| ("-c"|"--reverse-line-content")::rest ->
parseArgs { settings with ReverseLineContent = true } rest
| _ ->
printUsage()
exit 1
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
let rec parseArgs settings args =
if args = [] then settings else
match args with
| ("-i"|"--input")::file::rest ->
{ settings with InputFile = Some file }, rest
| ("-o"|"--output")::file::rest ->
{ settings with OutputFile = Some file }, rest
| ("-l"|"--reverse-line-order")::rest ->
{ settings with ReverseLineOrder = true }, rest
| ("-c"|"--reverse-line-content")::rest ->
{ settings with ReverseLineContent = true }, rest
| _ ->
printUsage()
exit 1
||> parseArgs
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
let reverseFile settings =
use reader =
match settings.InputFile with
| Some file -> new StreamReader(file) :> TextReader
| None -> stdin
use writer =
match settings.OutputFile with
| Some file -> new StreamWriter(file) :> TextWriter
| None -> stdout
let revString line = String(line |> Array.ofSeq |> Array.rev)
let lines =
[| while reader.Peek() <> -1 do
yield reader.ReadLine() |]
|> Array.map (if settings.ReverseLineContent then revString else id)
let lines = if settings.ReverseLineOrder then Array.rev lines else lines
for line in lines do
writer.WriteLine line
1: 2: 3: 4: 5: 6: 7: 8: 9:
type Expr =
| Con of int
| Var of string
| Add of Expr * Expr
| Sub of Expr * Expr
| Mult of Expr * Expr
| Div of Expr * Expr
| Power of Expr * Expr
| Neg of Expr
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
let rec print expr =
match expr with
| Add(x, y) -> sprintf "(%s + %s)" (print x) (print y)
| Sub(x, y) -> sprintf "(%s - %s)" (print x) (print y)
| Mult(x, y) -> sprintf "(%s * %s)" (print x) (print y)
| Div(x, y) -> sprintf "(%s / %s)" (print x) (print y)
| Power(x, y) -> sprintf "(%s ** %s)" (print x) (print y)
| Neg x -> sprintf "-(%s)" (print x)
| Var x -> x
| Con x -> string x
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
let rec deriv var expr =
let d = deriv var
match expr with
| Var var -> Con 1 // Identity Rule
| Con x -> Con 0 // Constant Rule
| Mult(Con x, y) | Mult(y, Con x) -> Con x // Constant Factor Rule
| Add(x, y) -> d x + d y // Sum Rule
| Sub(x, y) -> d x - d y // Difference Rule
| Mult(x, y) -> d x * y + x * d y // Product Rule
| Div(x, y) -> (d x * y - x * d y) / y ** 2 // Quotient Rule
| Power(var, Con x) -> x * var ** (x - 1) // Elementary Power Rule
| _ -> failwith "Sorry, don't know how to differentiate that!"
1: 2: 3: 4:
let showNum input =
match Int32.TryParse input with
| true, value -> string value
| false, _ -> "NaN"
Good for creating reusable match rules or conversions.
1: 2: 3: 4: 5: 6: 7: 8: 9:
let (|Number|_|) input =
match Int32.TryParse input with
| true, value -> Some value
| false, _ -> None
let showNum input =
match input with
| Number n -> string n
| _ -> "NaN"
Good for classifying data into one or more categories.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
let (|Negative|Positive|Zero|) num =
match num with
| 0 -> Zero
| _ when num > 0 -> Positive num
| _ when num < 0 -> Negative -num
let showNum input =
match input with
| Zero -> "Zero!"
| Positive n -> sprintf "Positive %d" n
| Negative n -> sprintf "Negative %d" n
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
let rec parseArgs settings args =
if args = [] then settings else
match args with
| ("-i"|"--input")::file::rest when not (file.StartsWith "-") ->
{ settings with InputFile = Some file }, rest
| ("-o"|"--output")::file::rest when not (file.StartsWith "-") ->
{ settings with OutputFile = Some file }, rest
| ("-l"|"--reverse-line-order")::rest ->
{ settings with ReverseLineOrder = true }, rest
| ("-c"|"--reverse-line-content")::rest ->
{ settings with ReverseLineContent = true }, rest
| _ ->
printUsage()
exit 1
||> parseArgs
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
let (|File|_|) input =
if input.StartsWith "-" then None
else Some input
let rec parseArgs settings args =
if args = [] then settings else
match args with
| ("-i"|"--input")::File file::rest ->
{ settings with InputFile = Some file }, rest
| ("-o"|"--output")::File file::rest ->
{ settings with OutputFile = Some file }, rest
| ("-l"|"--reverse-line-order")::rest ->
{ settings with ReverseLineOrder = true }, rest
| ("-c"|"--reverse-line-content")::rest ->
{ settings with ReverseLineContent = true }, rest
| _ ->
printUsage()
exit 1
||> parseArgs
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
let (|File|) input =
if input.StartsWith "-" then
failwith "'%s' is not a valid filename." input
else input
let rec parseArgs settings args =
if args = [] then settings else
match args with
| ("-i"|"--input")::File file::rest ->
{ settings with InputFile = Some file }, rest
| ("-o"|"--output")::File file::rest ->
{ settings with OutputFile = Some file }, rest
| ("-l"|"--reverse-line-order")::rest ->
{ settings with ReverseLineOrder = true }, rest
| ("-c"|"--reverse-line-content")::rest ->
{ settings with ReverseLineContent = true }, rest
| _ ->
printUsage()
exit 1
||> parseArgs
Many other places in F# are "secretly" pattern matches.
The expression
1: 2:
let x = 2 ...
is really like
1:
match 2 with x -> ...
So you can do
1: 2:
let x, y = 1, 2 let Number n = "5"
Note however
1: 2:
let [x; y] = list let Some x = option
will cause incomplete match warnings since the is only one case.
1: 2: 3: 4: 5: 6: 7: 8:
let nameAges = [
"John", 22
"Jane", 43
"George", 68
]
for name, age in nameAges do
printfn "The age of %s is %d" name age
Pattern matching in lambda parameters
1: 2: 3:
nameAges
|> Seq.iter (fun (name, age) ->
printfn "The age of %s is %d" name age)
Calculating an endpoint to a line
1: 2: 3: 4: 5: 6: 7:
let endpoint (startX, startY) length angle =
x + length * cos angle, y + length * sin angle
let start = (5., 5.)
let length = 10.
let angle = 0.5 * Math.PI
let endX, endY = endpoint start length angle
Normal function parameters are actually "variable" patterns.
Exception handling in F# is just pattern matching.
1: 2: 3: 4: 5:
try
File.ReadAllLines "data.txt"
with
| :? IOException as e ->
printfn "An IO exception occurred: %s" e.Message
Solve Fizzbuzz using pattern matching.
Rules for FizzBuzz:
Example Output (1-15): 1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz
Thanks to: