On this talk, we will demonstrate the power of types and advanced type systems. We will see:
- how to combine the safety of static typing with the ease of -
- development typically only offered by dynamically typed languages.
- how to model your domain with improved correctness based on phantom types
- how to express side effects
17. 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
18. type UserName = {
firstName: string
lastName: string
}
type Shape =
| Circle of int
| Rectangle of int * int
19. type UserName = {
firstName: string;
lastName: string
}
let a = { firstName = "a"; lastName = "b" }
let b = { firstName = "a"; lastName = "b" }
if a = b then "equals" // true
20. type Money = {
amount: decimal
currency: Currency
}
let a = { amount = 10; currency = USD }
let b = { amount = 10; currency = USD }
if a = b then "equals" // true
22. 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)
23.
24. 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; }
}
25. 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)
}
26. 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
34. 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
35. 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
36. 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
37. 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.unpaidItems
Cart.Active { state with unpaidItems = newList }
38. 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 }
39. 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 }
40. 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 }
41. 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 anymore
let activeState = addToActiveState(paidState, item)
43. let failingFunc num =
let x = raise (new System.Exception("fail!"))
try
let y = 42 + 5 + num
x + y
with
e -> 43
44. /// Represents the result of a computation
type Result<'ok, 'msg> =
| Ok of 'ok * 'msg list
| Fail of 'msg list
45. 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)
46. 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
47. 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
48.
49. 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.
50. 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.Today
then ok { invoice with dueDate = newDueDate }
else fail "Due date must be in future."
52. type PastDate = PastDate of DateTime
type CurrentDate = CurrentDate of DateTime
type 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 }
53. Problem: Language do not integrate information
- We need to bring information into the language…