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: