![Page 1: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/1.jpg)
TDD with F# (since 2003)
Anton Moldovan (@antyadev)
SBTech
Twitter: https://twitter.com/antyaDev
Github: https://github.com/antyaDev
![Page 2: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/2.jpg)
About me:@AntyaDev
like types*
![Page 3: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/3.jpg)
![Page 4: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/4.jpg)
![Page 5: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/5.jpg)
@ploeh
![Page 6: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/6.jpg)
@ploeh
![Page 7: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/7.jpg)
@ploeh
![Page 8: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/8.jpg)
@ploeh
![Page 9: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/9.jpg)
Free monads
![Page 10: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/10.jpg)
![Page 11: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/11.jpg)
![Page 12: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/12.jpg)
In OOP you by default thinking about
abstraction extensibility
![Page 13: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/13.jpg)
In FP you by default thinking about
purity composabilitycorrectness
![Page 14: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/14.jpg)
In FP you build your ideal, private worldwhere
you know everything
![Page 15: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/15.jpg)
Pure Domain
![Page 16: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/16.jpg)
![Page 17: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/17.jpg)
If you are coming from an object-oriented design background, one of the paradigm shifts involved in "thinking functionally" is to change how you think about types.
A well designed object-oriented program will have:• a strong focus on behavior rather than data, • will use a lot of polymorphism (interfaces),• will try to avoid having explicit knowledge of the actual concrete
classes being passed around.
A well designed functional program, on the other hand, will have a strong focus on data types rather than behavior
![Page 18: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/18.jpg)
type UserName = {firstName: stringlastName: string
}
type Shape =| Circle of int| Rectangle of int * int
![Page 19: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/19.jpg)
type UserName = {firstName: string;lastName: string
}
let a = { firstName = "a"; lastName = "b" }let b = { firstName = "a"; lastName = "b" }
if a = b then "equals" // true
![Page 20: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/20.jpg)
type Money = {amount: decimalcurrency: Currency
}
let a = { amount = 10; currency = USD }let b = { amount = 10; currency = USD }
if a = b then "equals" // true
![Page 21: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/21.jpg)
type Shape =| Rectangle = 0| Circle = 1| Prism = 2
![Page 22: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/22.jpg)
type Shape =| Rectangle of width:float * length:float| Circle of radius:float| Prism of width:float * float * height:float
let rectangle = Rectangle(width = 6.2, length = 5.5)
![Page 23: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/23.jpg)
![Page 24: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/24.jpg)
anyone can set this to ‘true’
Rule 1: if the email is changed, the verified flag must be reset to ‘false’.
Rule 2: the verified flag can only be set by a special verification service.
Rule 3: we have 5 services which works only for verified email and 5 services which works for invalid email.
class EmailContact{
public string EmailAddress { get; set; }public bool IsEmailVerified { get; set; }
}
![Page 25: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/25.jpg)
Rule 3: we have - 5 services which works only for verified email and - 5 services which works for invalid email.
if (emailContract.IsEmailVerified)
void SendEmailToApprove(EmailContact emailContract){
if (emailContract.IsEmailVerified)}
void SendEmailToReject(EmailContact emailContract){
if (emailContract.IsEmailVerified)}
void SendEmailToConfirm(EmailContact emailContract){
if (emailContract.IsEmailVerified)}
void SendEmailToLinkedin(EmailContact emailContract){
if (emailContract.IsEmailVerified)}
![Page 26: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/26.jpg)
type ValidEmail = { email: string }type InvalidEmail = { email: string }
type Email =| Valid of ValidEmail| Invalid of InvalidEmail
let sendEmailToLinkedin (email: ValidEmail) = ...
You need only one dispatch in one place
![Page 27: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/27.jpg)
![Page 28: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/28.jpg)
•
•
•
![Page 29: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/29.jpg)
public class NaiveShoppingCart<TItem>{
private List<TItem> items;private decimal paidAmount;
public NaiveShoppingCart(){
this.items = new List<TItem>();this.paidAmount = 0;
}
/// Is cart paid for?public bool IsPaidFor => this.paidAmount > 0;
public IEnumerable<TItem> Items => this.items;
public void AddItem(TItem item){
if (!this.IsPaidFor) this.items.Add(item); }
/// remove item only if not paid for
![Page 30: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/30.jpg)
if (!this.IsPaidFor) { do something }
![Page 31: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/31.jpg)
public class NaiveShoppingCart<TItem>{
private List<TItem> items;public bool IsPaidFor => this.paidAmount > 0;
public bool IsPaidFor => this.paidAmount > 0;public bool IsConfirmedByUser => _isConfirmedByUser;public bool IsApproved => IsPaidFor && _isValid;public bool IsCanceledByUser => _isCanceled && _user != null;public bool IsCanceledAuto => _isCanceled || _user == null && _system != null;public bool IsCanceledAdmin => _isCanceled || _user == null && _system == null && _admin != null;
public Status GetStatus(){
if (_isCanceled && items.Count > 0)return Status.Invalid;
else if (items.Count > 0)return Status.Active;
return Status.Empty;}
public void Approve(){
if (_isCanceled) throw new Exception();
if (items.Count > 0)}
what about recently added IsCanceledByAdmin?
![Page 32: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/32.jpg)
![Page 33: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/33.jpg)
•
•
•
![Page 34: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/34.jpg)
type CartItem = string // placeholder for a more complicated type
type EmptyState = NoItems
type ActiveState = { unpaidItems: CartItem list; }
type PaidForState = { paidItems: CartItem list; payment: decimal}
type Cart =| Empty of EmptyState| Active of ActiveState| PaidFor of PaidForState
![Page 35: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/35.jpg)
type EmptyState = NoItems
type ActiveState = { unpaidItems: CartItem list; }
type PaidForState = { paidItems: CartItem list; payment: decimal }
type Cart =| Empty of EmptyState| Active of ActiveState| PaidFor of PaidForState
// =============================// operations on empty state// =============================
let addToEmptyState (item: CartItem) : Cart.Active =Cart.Active { unpaidItems = [item] } // a new Active Cart
![Page 36: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/36.jpg)
type EmptyState = NoItems
type ActiveState = { unpaidItems: CartItem list; }
type PaidForState = { paidItems: CartItem list; payment: decimal }
type Cart =| Empty of EmptyState| Active of ActiveState| PaidFor of PaidForState
// =============================// operation on empty state// =============================
let addToEmptyState item ={ unpaidItems = [item] } // returns a new Active Cart
![Page 37: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/37.jpg)
type EmptyState = NoItems
type ActiveState = { unpaidItems: CartItem list; }
type PaidForState = { paidItems: CartItem list; payment: decimal }
type Cart =| Empty of EmptyState| Active of ActiveState| PaidFor of PaidForState
// =============================// operation on active state// =============================
let addToActiveState (state: ActiveState, itemToAdd: CartItem) =let newList = itemToAdd :: state.unpaidItemsCart.Active { state with unpaidItems = newList }
![Page 38: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/38.jpg)
type EmptyState = NoItems
type ActiveState = { unpaidItems: CartItem list; }
type PaidForState = { paidItems: CartItem list; payment: decimal }
type Cart =| Empty of EmptyState| Active of ActiveState| PaidFor of PaidForState
// =============================// operation on active state// =============================
let addToActiveState state itemToAdd =let newList = itemToAdd :: state.unpaidItems{ state with unpaidItems = newList }
![Page 39: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/39.jpg)
type EmptyState = NoItems
type ActiveState = { unpaidItems: CartItem list; }
type PaidForState = { paidItems: CartItem list; payment: decimal }
type Cart =| Empty of EmptyState| Active of ActiveState| PaidFor of PaidForState
let removeFromActiveState state itemToRemove =let newList = state.unpaidItems
|> List.filter (fun i -> i <> itemToRemove)
match newList with| [] -> Cart.Empty NoItems| _ -> Cart.Active { state with unpaidItems = newList }
![Page 40: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/40.jpg)
type EmptyState = NoItems
type ActiveState = { unpaidItems: CartItem list; }
type PaidForState = { paidItems: CartItem list; payment: decimal }
type Cart =| Empty of EmptyState| Active of ActiveState| PaidFor of PaidForState
let payForActiveState state amount =Cart.PaidFor { paidItems = state.unpaidItems
payment = amount }
![Page 41: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/41.jpg)
type EmptyState = NoItems
type ActiveState = { unpaidItems: CartItem list; }
type PaidForState = { paidItems: CartItem list; payment: decimal}
type Cart =| Empty of EmptyState| Active of ActiveState| PaidFor of PaidForState
let item = “test_product”let activeState = addToEmptyState(item)let paidState = payForActiveState(activeState, 10)
// compile error, your state is not active anymorelet activeState = addToActiveState(paidState, item)
![Page 42: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/42.jpg)
errors
![Page 43: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/43.jpg)
let failingFunc num =let x = raise (new System.Exception("fail!"))try
let y = 42 + 5 + numx + y
withe -> 43
![Page 44: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/44.jpg)
/// Represents the result of a computationtype Result<'ok, 'msg> =
| Ok of 'ok * 'msg list| Fail of 'msg list
![Page 45: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/45.jpg)
type Request = { name: string; email: string }
let validateInput input =if input.name = ""
then Fail("Name must not be blank")elif input.email = ""
then Fail("Email must not be blank")else Ok(input)
![Page 46: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/46.jpg)
type Request = { name: string; email: string }
let validateInput input =if input.name = ""
then fail "Name must not be blank"elif input.email = ""
then fail "Email must not be blank"else ok input
![Page 47: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/47.jpg)
let validate1 input =if input.name = "" then fail "Name must not be blank“else ok input
let validate2 input =if input.name.Length > 50 then fail "Name must not be longer than 50 chars"else ok input
let validate3 input =if input.email = "" then fail "Email must not be blank"else ok input
let validRequest = validate1 >>= validate2 >>= validate3 >>= validate4
![Page 48: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/48.jpg)
![Page 49: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/49.jpg)
In functional programming we strive to write side-effect free applications. In other words, all the functions of the application should be pure. However, completely side-effect free applications are mostly useless, so the next best thing is to minimize the amount of side-effects, make them explicit and push them as close to the boundaries of the application as possible.
![Page 50: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/50.jpg)
Let’s see an example in invoicing domain. When changing a due date of an invoice we want to check that the new due date is in the future. We could implement it like this:
let changeDueDate (newDueDate:DateTime, invoice) =
if newDueDate > System.DateTime.Todaythen ok { invoice with dueDate = newDueDate }
else fail "Due date must be in future."
![Page 51: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/51.jpg)
let changeDueDate (newDueDate:DateTime,currentDate:DateTime, invoice) =
if newDueDate > currentDatethen ok { invoice with dueDate = newDueDate }
else fail "Due date must be in future."
![Page 52: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/52.jpg)
type PastDate = PastDate of DateTimetype CurrentDate = CurrentDate of DateTimetype FutureDate = FutureDate of DateTime
type Date =| Past of PastDate| Current of CurrentDate| Future of FutureDate
let changeDueDate (newDueDate:FutureDate, invoice) ={ invoice with DueDate = Date newDueDate }
![Page 53: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/53.jpg)
Problem: Language do not integrate information
- We need to bring information into the language…
![Page 54: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/54.jpg)
![Page 55: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/55.jpg)
![Page 56: Антон Молдован "Type driven development with f#"](https://reader030.vdocuments.pub/reader030/viewer/2022013113/58e4a5bd1a28abf5428b6efb/html5/thumbnails/56.jpg)