Upload
others
View
12
Download
0
Embed Size (px)
Citation preview
MONOIDSErm... what?
SIMPLE RULESTake an operation (Op)Take a type ('T)Op : 'T -> 'T -> 'T (Closure)Op t1 (Op t2 t3) = Op (Op t1 t2) t3(Associativity)There is a t0 such that: Op t0 t1 = Op t1 t0 (Zero)
EXAMPLE 1 - INTEGER ADDITION1 + (2 + 3) = (1 + 2) + 3
1 + 0 = 0 + 1
EXAMPLE 2 - STRING CONCATINATION("Bob" + "Fred") + "Smith" = "Bob" + ("Fred" + "Smith")
"Bob" + "" = "" + "Bob"
POP QUIZ 1 - EMAIL SUCCESS COUNT1: 2: 3: 4: 5: 6: 7:
type EmailsSent = { SuccessCount : int FailCount : int }
let addEmailsSent es es' = { SuccessCount = es.SuccessCount + es'.SuccessCount FailCount = es.FailCount + es'.FailCount }
Monoid? Yes or no?
POP QUIZ 2 - MEANS1: 2: let mean (x : int) (y : int) = (float x + float y) / 2.0
Monoid? Yes or no?
FAIL!
MEANSNo closure: int -> int -> floatNot associative: mean 1 (mean 2 3) <> mean (mean 1 2) 3... even if you ignore the type error
BUT...1: 2: 3: 4: 5: 6: 7:
type MeanTracker = { Total : int Divisor : int }
let addMean mt mt' = { Total = mt.Total + mt'.Total Divisor = mt.Divisor + mt'.Divisor }
THAT'S GREAT......but why are we doing this?
BECAUSE LISTS.(or IEnumerable<'T>, if you're that way inclined)
REDUCEOF MAP/REDUCE FAME
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
let currentMean = [{ Total = 10; Divisor = 1 } { Total = 20; Divisor = 1 }] |> List.reduce addMean
// Some more data comes in... let newMean = [currentMean { Total = 15; Divisor = 1}] |> List.reduce addMean
Incremental and parallel processing are trivial
THIS COMPILES...1: 2: 3:
// No data yet... [] |> List.reduce addMean
PARTIAL FUNCTIONS ARE EVILA partial function is one that can't create a valid output forevery "valid" inputReduce is a partial function as it can't operate on emptylists
LET'S TURN IT UP TO ELEVEN
FOLD1: 2: 3:
// Still no data [] |> List.fold addMean { Total = 0; Divisor = 0 }
SUMMARY SO FARIf you have, or you can make a monoid:
You can always reduce lists down to a single summaryvalueYou can incrementally process the listFor (lots) more on monoids, read the series by ScottWlaschin
EVENT SOURCING?Nearly... but �rst!
GENERALISING FOLDaddMean had type: MeanTracker -> MeanTracker -> MeanTrackerBut the �rst parameter to fold has signature: 'State ->'T -> 'State
EXAMPLE "PROJECTION" FOLD1: 2: 3:
// Not a monoid operator - no closure let emailsSent total es = total + es.SuccessCount
INCREMENTAL SEND TOTALS! 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
let currentOutput = [{ SuccessCount = 10; FailCount = 0 } { SuccessCount = 22; FailCount = 1 }] |> List.fold emailsSent 0
// More emails get sent let newCurrentOutput = [{ SuccessCount = 112; FailCount = 2 } { SuccessCount = 100; FailCount = 5 }] |> List.fold emailsSent currentOutput
And then...
...the customer tells you they want to know the percentagefailure rate of sends. Preferably with historic data. And, of
course, live updates.
NO PROBLEM!1: 2: 3: 4:
// Remember our MeanTracker object?let averageEmailSuccess mt es = { Total = mt.Total + es.SuccessCount Divisor = mt.Divisor + (es.SuccessCount + es.FailCount) }
INCREMENTAL AVERAGE SUCCESS RATES! 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
// We already have this data let currentRate = [{ SuccessCount = 10; FailCount = 0 } { SuccessCount = 22; FailCount = 1 } { SuccessCount = 112; FailCount = 2 } { SuccessCount = 100; FailCount = 5 }] |> List.fold averageEmailSuccess { SuccessCount = 0; FailCount = 0 }
// Now more starts coming in let newCurrentRate = [{ SuccessCount = 15; FailCount = 4 } { SuccessCount = 30; FailCount = 0 }] |> List.fold averageEmailSuccess currentRate
EVENT SOURCINGFinally...
EVENT SOURCING BASICSStore the domain events as "lists" (normally called streams)Build projections of them using folds
DOMAIN EVENTSA thing that has already happenedEmailSent; InvoicePaidDomain events can't fail - they've already happened!Compare with command: SendEmail; PayInvoice
SIMPLE INTERFACEno object relational impedance mismatch
SIMPLE INTERFACE1: 2: 3: 4: 5: 6: 7: 8: 9:
open System
type IRepository<'state, 'event> = abstract member LoadAggregate<'state> : Guid -> Async<'state * int> abstract member RefreshAggregate<'state> : Guid -> int -> 'state -> Async<'state * abstract member AppendEvents<'event> : Guid -> int -> 'event seq -> Async<int>
LET'S SEE AN EXAMPLE IN ACTION...