ML 21: Experience Report:
Domain Modeling with F#
@ScottWlaschin
fsharpforfunandprofit.com
Slide 2
Slide 2 text
Languages with
Mathematical focus
Languages with
Business focus
Proof assistants
FP languages
COBOL
Java/C#
F#
Slide 3
Slide 3 text
Business-focused development
• Understanding the domain is critical
– “Correctness” is a lesser goal
• Responding to feedback
– Need to adjust domain model quickly and safely
Slide 4
Slide 4 text
F# for business-focused development
• Readable by non-developers
– And easy to collaborate with them
• Composable types for domain modeling
– And type checking makes it safe to refactor
• Eliminating errors through design
– “Make illegal states unrepresentable”
Slide 5
Slide 5 text
Collaborating with non-developers
(Domain-driven design)
Slide 6
Slide 6 text
Waterfall methodology
Slide 7
Slide 7 text
Agile methodology
Slide 8
Slide 8 text
Agile methodology
Slide 9
Slide 9 text
Domain-Driven Design
Slide 10
Slide 10 text
No content
Slide 11
Slide 11 text
No content
Slide 12
Slide 12 text
Can you really make code
represent the domain?
Slide 13
Slide 13 text
What non-developers think source code looks like
Slide 14
Slide 14 text
Shared language
What DDD source code
should look like
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two | Three | Four | Five | Six | Seven | Eight
| Nine | Ten | Jack | Queen | King
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = { Name:string; Hand:Hand }
type Game = { Deck:Deck; Players: Player list }
type Deal = Deck –› (Deck * Card)
type PickupCard = (Hand * Card) –› Hand
Slide 15
Slide 15 text
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two | Three | Four | Five | Six | Seven | Eight
| Nine | Ten | Jack | Queen | King
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = { Name:string; Hand:Hand }
type Game = { Deck:Deck; Players: Player list }
type Deal = Deck –› (Deck * Card)
type PickupCard = (Hand * Card) –› Hand
Slide 16
Slide 16 text
module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two | Three | Four | Five | Six | Seven | Eight
| Nine | Ten | Jack | Queen | King
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = { Name:string; Hand:Hand }
type Game = { Deck:Deck; Players: Player list }
type Deal = Deck –› (Deck * Card)
type PickupCard = (Hand * Card) –› Hand
Non-programmers can provide
useful feedback
Slide 17
Slide 17 text
Rapid feedback during
the design stage
Slide 18
Slide 18 text
Creating the domain model is an interactive process
Not just for developers. Everyone participates
Slide 19
Slide 19 text
...
type Deck = Card list
type Deal = Deck –› (Deck * Card)
Slide 20
Slide 20 text
...
type Deck = Card list
type Deal = ShuffledDeck –› (ShuffledDeck * Card)
Slide 21
Slide 21 text
...
type Deck = Card list
type Deal = ShuffledDeck –› (ShuffledDeck * Card)
type ShuffledDeck = Card list
Slide 22
Slide 22 text
...
type Deck = Card list
type Deal = ShuffledDeck –› (ShuffledDeck * Card)
type ShuffledDeck = Card list
type Shuffle = Deck –› ShuffledDeck
Slide 23
Slide 23 text
...
type Deck = Card list
type Deal = ShuffledDeck –› (ShuffledDeck * Card)
type ShuffledDeck = Card list
type Shuffle = Deck –› ShuffledDeck
Slide 24
Slide 24 text
Domain modeling
with an algebraic type system
Slide 25
Slide 25 text
I explain that new types are built
from smaller types by:
• Composing with AND
• Composing with OR (“choices”)
Slide 26
Slide 26 text
Example of requirements:
We accept three forms of payment:
Cash, PayPal, or Card.
For Cash we don't need any extra information
For PayPal we need a email address
For Cards we need a card type and card number
Slide 27
Slide 27 text
type EmailAddress = string
type CardNumber = string
Implement by composing types, like this:
Slide 28
Slide 28 text
type EmailAddress = ...
type CardNumber = …
type CardType = Visa | Mastercard
type CreditCardInfo = {
CardType : CardType
CardNumber : CardNumber
}
Slide 29
Slide 29 text
type EmailAddress = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| PayPal of EmailAddress
| Card of CreditCardInfo
Slide 30
Slide 30 text
type EmailAddress = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| PayPal of EmailAddress
| Card of CreditCardInfo
Slide 31
Slide 31 text
type EmailAddress = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| PayPal of EmailAddress
| Card of CreditCardInfo
type PaymentAmount = decimal
type Currency = EUR | USD
Slide 32
Slide 32 text
type EmailAddress = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| PayPal of EmailAddress
| Card of CreditCardInfo
type PaymentAmount = decimal
type Currency = EUR | USD
type Payment = {
Amount : PaymentAmount
Currency : Currency
Method : PaymentMethod }
Slide 33
Slide 33 text
type EmailAddress = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| PayPal of EmailAddress
| Card of CreditCardInfo
type PaymentAmount = decimal
type Currency = EUR | USD
type Payment = {
Amount : PaymentAmount
Currency : Currency
Method : PaymentMethod }
Slide 34
Slide 34 text
Eliminating errors through design
“Make illegal states unrepresentable”
Slide 35
Slide 35 text
Business Rules:
• 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
type EmailContactInfo = {
EmailAddress : string
IsEmailVerified : bool }
Slide 36
Slide 36 text
type EmailAddress = EmailAddress of string
// with associated smart constructor
type VerifiedEmail = VerifiedEmail of EmailAddress
type VerificationService =
(EmailAddress * VerificationHash) –› VerifiedEmail option
type EmailContactInfo =
| Unverified of EmailAddress
| Verified of VerifiedEmail
Here’s the refactored model
Slide 37
Slide 37 text
type EmailAddress = EmailAddress of string
// with associated smart constructor
type VerifiedEmail = VerifiedEmail of EmailAddress
type VerificationService =
(EmailAddress * VerificationHash) –› VerifiedEmail option
type EmailContactInfo =
| Unverified of EmailAddress
| Verified of VerifiedEmail
Slide 38
Slide 38 text
type EmailAddress = EmailAddress of string
// with associated smart constructor
type VerifiedEmail = VerifiedEmail of EmailAddress
type VerificationService =
(EmailAddress * VerificationHash) –› VerifiedEmail option
type EmailContactInfo =
| Unverified of EmailAddress
| Verified of VerifiedEmail
Slide 39
Slide 39 text
type EmailAddress = EmailAddress of string
// with associated smart constructor
type VerifiedEmail = VerifiedEmail of EmailAddress
type VerificationService =
(EmailAddress * VerificationHash) –› VerifiedEmail option
type EmailContactInfo =
| Unverified of EmailAddress
| Verified of VerifiedEmail
Those business rules are automatically enforced by the design!
Slide 40
Slide 40 text
Nice-to-have language improvements
• Zero-cost wrappers (e.g. newtype)
• An easier way to do “smart constructors” that
perform validation
• An easier way to enforce constraints
(refinement types?)
• A more fine-grained way of controlling access
to constructors (e.g. the VerifiedEmail
constructor)
Slide 41
Slide 41 text
Summary
• Algebraic types are excellent for
business-focused domain modeling
• Non-developers can read and contribute
• Lots of potential for ML languages here!
Thanks!