- Hausaufgaben (8..10/10)
- Bank Account
- Accumulate
- Space Age
- Domain Driven Design (DDD)
- Property Based Testing
type OpenAccount =
{ mutable Balance: decimal }
type Account =
| Closed
| Open of OpenAccount
let mkBankAccount() = Closed
let openAccount account =
match account with
| Closed -> Open { Balance = 0.0m }
| Open _ -> failwith "Account is already open"
type OpenAccount =
{ mutable Balance: decimal }
type Account =
| Closed
| Open of OpenAccount
val mkBankAccount: unit -> Account
val openAccount: account: Account -> Account
let closeAccount account =
match account with
| Open _ -> Closed
| Closed -> failwith "Account is already closed"
let getBalance account =
match account with
| Open openAccount -> Some openAccount.Balance
| Closed -> None
let updateBalance change account =
match account with
| Open openAccount ->
lock (openAccount) (fun _ ->
openAccount.Balance <- openAccount.Balance + change
Open openAccount)
| Closed -> failwith "Account is closed"
let account = mkBankAccount() |> openAccount
let updateAccountAsync =
async { account |> updateBalance 1.0m |> ignore }
let ``updated from multiple threads`` =
updateAccountAsync
|> List.replicate 1000
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
let test1 = getBalance account = (Some 1000.0m)
let rec accumulateR func input acc =
match input with
| [] -> acc |> List.rev
| head::tail -> accumulateR func tail (func head :: acc)
let accumulate func input = accumulateR func input []
let test1 = accumulate (fun x -> x * x) [1; 2; 3]
let test2 = accumulate (fun (x:string) -> x.ToUpper()) ["hello"; "world"]
val accumulateR: func: ('a -> 'b) -> input: 'a list -> acc: 'b list -> 'b list
val accumulate: func: ('a -> 'b) -> input: 'a list -> 'b list
val test1: int list = [1; 4; 9]
val test2: string list = ["HELLO"; "WORLD"]
type Planet =
| Mercury
| Venus
| Earth
| Mars
| Jupiter
| Saturn
| Uranus
| Neptune
let orbitalPeriodRelativeToEarthOn planet =
match planet with
| Mercury -> 0.2408467
| Venus -> 0.61519726
| Earth -> 1.0
| Mars -> 1.8808158
| Jupiter -> 11.862615
| Saturn -> 29.447498
| Uranus -> 84.016846
| Neptune -> 164.79132
open System
[<Literal>]
let SecondsInOneEarthYear = 31557600.0
let secondsInAYearOn planet =
SecondsInOneEarthYear * orbitalPeriodRelativeToEarthOn planet
let round (number : float) = Math.Round(number, 2)
let age (planet: Planet) (seconds: int64): float =
float seconds / (secondsInAYearOn planet)
|> round
let test1 = age Earth 1000000000L
[<Literal>]
val SecondsInOneEarthYear: float = 31557600
val secondsInAYearOn: planet: Planet -> float
val round: number: float -> float
val age: planet: Planet -> seconds: int64 -> float
val test1: float = 31.69
- nutze exercism.io!
- Vermeide
mutable
!! - nur wichtiges verdient einen Namen
- Vertraue der Pipe (
>>
,|>
, …)!! - If-Then-Else mit Boolean ist unnötig
- Parametrisiere!
- If-Then-Else vermeiden … besser
match
! - Be lazy! (vermeide
for
-loops) - Troubleshooting F#
- F#-Styleguide
You’re bound to be unhappy if you optimize everything.
\null\hfill – Donald Knuth
\null\hfill–Scott Wlashin: F# for Fun and Profit
- Verwende die Sprache der Domäne (ubiquitous Language)
- Values und Entities
- der Code ist das Design (kein UML nötig)
- Design mit (algebraischen) Typen
- Option statt Null
- DU statt Vererbung
- illegale Konstellationen sollten nicht repräsentierbar sein!
Are you quite sure that all those bells and whistles, all those wonderful facilities of your so called powerful programming languages, belong to the solution set rather than the problem set?
\null\hfill – Edsger Dijkstra
A Contact has
- a personal name
- an optional email address
- an optional postal address
- Rule: a contact must have an email or a postal address
A Personal Name consists of a first name, middle initial, last name
- Rule: the first name and last name are required
- Rule: the middle initial is optional
- Rule: the first name and last name must not be more than 50 chars
- Rule: the middle initial is exactly 1 char, if present
A postal address consists of a four address fields plus a country
- Rule: An Email Address can be verified or unverified
The payment taking system should accept:
- Cash
- Credit cards
- Cheques
- Paypal
- Bitcoin
A payment consists of a:
- payment
- non-negative amount
After designing the types, create functions that will:
- print a payment method
- print a payment, including the amount
- create a new payment from an amount and method
Much C# code has implicit states that you can recognize by fields called "IsSomething", or nullable date.
This is a sign that states transitions are present but not being modelled properly.
Create types that model an e-commerce shopping cart.
- Rule: "You can't remove an item from an empty cart"!
- Rule: "You can't change a paid cart"!
- Rule: "You can't pay for a cart twice"!
States are:
- Empty
- ActiveCartData
- PaidCartData
About the use of language: it is impossible to sharpen a pencil with a blunt axe.
It is equally vain to try to do it with ten blunt axes instead.
\null\hfill – Edsger Dijkstra
module Test1 =
open Implementation1
let tests = testList "implementation 1" [
test "add 1 3 = 4" {
let actual = add 1 3
let expected = 4
Expect.equal expected actual "" }
test "add 2 2 = 4" {
let actual = add 2 2
let expected = 4
Expect.equal expected actual "" } ];;
runTests expectoConfig Test1.tests
[23:23:18 INF] EXPECTO? Running tests... <Expecto>
[23:23:18 INF] EXPECTO! 2 tests run in 00:00:00.0117930 for implementation 1 – 2 passed, 0 ignored, 0 failed, 0 errored. Success! <Expecto>
val it: int = 0
module Implementation1 =
let add x y =
4
module Implementation1 =
val add: x: 'a -> y: 'b -> int
\null\hfill–Scott Wlashin: F# for Fun and Profit
let add1 x y = x + y
let add2 x y = x - y
let commutativeProperty f x y =
let result1 = f x y
let result2 = f y x
result1 = result2;;
FsCheck.Check.Quick (commutativeProperty add1)
FsCheck.Check.Quick (commutativeProperty add2)
FsCheck.Check.Quick (commutativeProperty add1)
FsCheck.Check.Quick (commutativeProperty add2);;
Ok, passed 100 tests.
Falsifiable, after 2 tests (2 shrinks) (StdGen (905515291, 297320661)):
Original:
1
-1
Shrunk:
0
1
val it: unit = ()
type Temp = F of int | C of float;;
let fGen =
FsCheck.Gen.choose(32,212)
|> FsCheck.Gen.map (fun i -> F i);;
let cGen =
FsCheck.Gen.choose(0,100)
|> FsCheck.Gen.map (fun i -> C (float i));;
let tempGen =
FsCheck.Gen.oneof [fGen; cGen]
let test = tempGen |> FsCheck.Gen.sample 0 20
test
let tempGen =
FsCheck.Gen.oneof [fGen; cGen]
let test = tempGen |> FsCheck.Gen.sample 0 20
test;;
val tempGen: Gen<Temp> = Gen <fun:Bind@88>
val test: Temp list =
[F 185; F 153; F 130; F 98; C 16.0; C 6.0; C 26.0; C 16.0; F 146; F 114;
F 91; F 59; C 56.0; C 46.0; C 66.0; C 56.0; C 76.0; F 75; F 52; F 201]
val it: Temp list =
[F 185; F 153; F 130; F 98; C 16.0; C 6.0; C 26.0; C 16.0; F 146; F 114;
F 91; F 59; C 56.0; C 46.0; C 66.0; C 56.0; C 76.0; F 75; F 52; F 201]
open FsCheck
let smallerThan81Property x = x < 81
FsCheck.Check.Quick smallerThan81Property
let test1 = FsCheck.Arb.shrink 100 |> Seq.toList
let test2 = FsCheck.Arb.shrink 88 |> Seq.toList
test2
Ok, passed 100 tests.
val smallerThan81Property: x: int -> bool
val test1: int list = [0; 50; 75; 88; 94; 97; 99]
val test2: int list = [0; 44; 66; 77; 83; 86; 87]
val it: int list = [0; 44; 66; 77; 83; 86; 87]
- Unterschiedlicher Weg, gleiches Ziel (Map(f)(Option(x))=Option(f x))
- Hin und Her (z.B. Reverse einer Liste)
- Invarianten (z.B. Länge einer Liste bei Sortierung)
- Idempotenz (noch einmal bringt nichts mehr)
- Divide et Impera! (teile und herrsche)
- Hard to prove, easy to verify (Primzahlzerlegung)
- Test-Orakel (z.B. einfach aber langsam)
- funktionales Domain Modeling (DDD)
- eigenschaftsbasiertes Testen (Property Based Testing)