time for functions
DESCRIPTION
It's time functional programming became the default programming paradigm in enterprise software. See how I did it using F# and what the effect was.TRANSCRIPT
![Page 1: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/1.jpg)
Time for Functions@simontcousins
![Page 2: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/2.jpg)
![Page 3: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/3.jpg)
![Page 4: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/4.jpg)
let rec qsort = function | [] -> [] | hd :: tl -> let lesser, greater = List.partition ((>=) hd) tl List.concat [qsort lesser; [hd]; qsort greater]
http://rosettacode.org/wiki/Sorting_algorithms/Quicksort#F.23
![Page 5: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/5.jpg)
private static List<int> quicksort(List<int> arr) { List<int> loe = new List<int>(), gt = new List<int>(); if (arr.Count < 2) return arr; int pivot = arr.Count / 2; int pivot_val = arr[pivot]; arr.RemoveAt(pivot); foreach (int i in arr) { if (i <= pivot_val) loe.Add(i); else if (i > pivot_val) gt.Add(i); } List<int> resultSet = new List<int>(); resultSet.AddRange(quicksort(loe)); if (loe.Count == 0){ loe.Add(pivot_val); }else{ gt.Add(pivot_val); } resultSet.AddRange(quicksort(gt)); return resultSet; }
![Page 6: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/6.jpg)
• Clear
• closer to a statement of the algorithm
• Concise
• less noise and accidental complexity
• Correct
• the type system works with the developer
Functional code is…
![Page 8: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/8.jpg)
Some new things to learn…
let rec qsort = function | [] -> [] | hd :: tl -> let lesser, greater = List.partition ((>=) hd) tl List.concat [qsort lesser; [hd]; qsort greater]
recursion pure functionsimmutable data
pattern matching
partial application
generics by defaulttype inference
higher-order functions
'a list -> 'a list when 'a : comparison
![Page 9: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/9.jpg)
let rec qsort = function | [] -> [] | hd :: tl -> let lesser, greater = List.partition ((>=) hd) tl List.concat [qsort lesser; [hd]; qsort greater]
gotcha!
![Page 10: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/10.jpg)
• Good for demos but what about large programs?
• Good for academics but what about us?
• Elegant code but what about performance?
• Does it work with legacy software?
• Where do I find functional programmers?
Some real-world concerns…
![Page 11: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/11.jpg)
![Page 12: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/12.jpg)
• lots of data
• forecasts
• metered data
• market data
• lots of types
• units of measure
• rates
• station parameters
Bespoke Enterprise Applications for the Energy Sector
• lots of computations
• schedules
• contracts
• analysis
… all changing over time
![Page 13: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/13.jpg)
ww
w.statnett.no
stay at 50Hz
by adjusting
these
THE ENERGY SECTOR
{to make this zero
![Page 14: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/14.jpg)
Project: Balancing Services• Blackstart
• BMSU
• Faststart
• Frequency Response
• Reactive Power
• STOR
contracted services provided by energy
companies to ensure the security and stability of
supply
http://www2.nationalgrid.com/uk/services/balancing-services/
![Page 15: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/15.jpg)
Old System • C#
• OO / Imperative
• Relational Database
• Untestable
• Slow
• Contracts not fully implemented
New System • F#
• Functional / OO / Imperative
• Document Store
• Highly testable
• Fast
• Contracts fully implemented
defeated by complexity tinyurl.com/stor-contract
![Page 16: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/16.jpg)
Dynamic API
Market API
Asset API
Contract Evaluation
APIContract Evaluation
Job API
View Model API
Document Store
Scheduler
Web UI
Test Console
![Page 17: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/17.jpg)
![Page 18: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/18.jpg)
Elegant
beautiful
simple
efficient
functional
![Page 19: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/19.jpg)
Not Elegant
“… but hey, it’s object-oriented!”
![Page 20: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/20.jpg)
• Struggles to be elegant
• abstraction event horizon
• top down designs
• coarse abstractions
• high ceremony
• data and behaviour tightly coupled
• Mutating state is a powerful and dangerous technique
• hard to reason about
• requires synchronised access
Real-world OO• Lots of accidental complexity
• ORMs, IoCs, Mocks, Design Patterns, UML …
• Hard to find developers who have mastered all of this
![Page 21: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/21.jpg)
me preparing
to mutate some state
![Page 22: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/22.jpg)
Not-Only SQL• most applications do not require the
flexibility a relational schema affords
• separate reporting concerns from application concerns
• applications are written in terms of aggregates not relational schemas
• persist aggregates
• making aggregates immutable affords
• as-of
• store inputs and outputs
• what-if, easy test and debug
fits well with functional programs
avoid accidental complexity: ORM,
normal form
![Page 23: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/23.jpg)
Contract Evaluation
API
Contract Evaluation
Job API
Document Store
Scheduler
JobRequest • RunID • Contract • Interval
Input • RunID • Contract Parameters • Asset Parameters • Dynamic Parameters • Market Parameters
JSON Documents
Output • RunID • Revenue • Additional Information
Output
Input
Pure Function
“Pure I/0”
![Page 24: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/24.jpg)
![Page 25: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/25.jpg)
Adoption: F#• Low risk
• Runs on CLR and mono
• Open source
• Inter-op with legacy software and libraries
• Back-out to C#
![Page 26: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/26.jpg)
Adoption: Developers• Self taught
• Hire good .NET developers, not language x developers
• .NET developer cutting F# production code in a week
• Functional programmer in a month
![Page 27: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/27.jpg)
Adoption: Managers
?
![Page 28: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/28.jpg)
Approachexploratory REPL driven
DRYer repeatedly re-factor
test driven documented development
![Page 29: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/29.jpg)
let config = new HttpSelfHostConfiguration(baseAddress) config.MapHttpAttributeRoutes() config.Formatters.JsonFormatter.SerializerSettings <- JsonSerializerSettings( PreserveReferencesHandling = PreserveReferencesHandling.None, Converters = [| Json.TupleConverter() Json.OptionConverter() Json.ArrayConverter() Json.ListConverter() Json.MapTypeConverter() Json.UnionTypeConverter() |]) config.DependencyResolver <- new UnityResolver(container)
Self-host Web API
F# type JSON converters
![Page 30: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/30.jpg)
HostFactory.Run(fun hc -> hc.UseLog4Net("log4net.config") hc.SetServiceName("Job.Api.Host") hc.SetDisplayName("E.ON Ancillary Services Job API Host") hc.SetDescription("An API service for Ancillary Services Jobs.") hc.RunAsNetworkService() |> ignore hc.Service<ApiService>(fun (s: ServiceConfigurator<ApiService>) -> s.ConstructUsing(fun (name: string) -> new ApiService(config)) |> ignore s.WhenStarted(fun (svc: ApiService) -> jobRequestQueue.Start() svc.Start()) |> ignore s.WhenStopped(fun (svc: ApiService) -> svc.Stop() jobRequestQueue.Stop()) |> ignore) |> ignore)
Topshelf Windows ServiceF# working with an
existing OO framework
![Page 31: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/31.jpg)
type ApiService(config: HttpSelfHostConfiguration) =! member val Server = new HttpSelfHostServer(config) with get, set! member this.Start() = this.Server.OpenAsync().Wait() member this.Stop() = if this.Server <> null then this.Server.CloseAsync().Wait() this.Server.Dispose()
Web API Servicean F# class!!!
![Page 32: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/32.jpg)
type JobController(log: ILog, jobRequestQueue: JobRequestQueue) = inherit ApiController()! [<Route("job/ping")>] member x.Get() = log.Debug("ping!!!") "pong" [<Route("job")>] member x.Post(request:JobRequest) = jobRequestQueue.Add(request)
Web API Controlleranother F# class!!!
![Page 33: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/33.jpg)
let requests = BlockingQueueAgent<JobRequest>(config.JobRequestQueueLength)! let workerName (i: int) = String.Format("worker[{0}]", i)! let worker (workerName: string) = async { while true do log.DebugFormat("{0} free", workerName) let! request = requests.AsyncGet() log.DebugFormat("{0} busy: job {1}", workerName, request.JobId) run request }! for i in 1 .. config.JobRequestWorkers do Async.Start(workerName i |> worker, CancellationToken.Token)! requests.Add(request)
Job Queue
github.com/fsprojects/fsharpx/blob/master/src/FSharpx.Core/Agents/BlockingQueueAgent.fs
agents: the safe way to manage state
async, efficient use of threads
scale workers
![Page 34: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/34.jpg)
async { let! input = buildRequest dataProvider let! output = sendToCompute input let result = buildModel input output do! store result }
Execute Jobcomposition of async computations
{
![Page 35: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/35.jpg)
async { use! response = httpClient.PostAsync(uri, toContent request) |> Async.AwaitTask return! response.EnsureSuccessStatusCode().Content.ReadAsStringAsync() |> Async.AwaitTask }
Post
F# async works with TPL Tasks
dispose of resource when done
![Page 36: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/36.jpg)
Async.RunSynchronously( post client config.JobUri request |> Async.Catch, config.Timeout)|> Choice.choice (fun _ -> log.InfoFormat("Executed Job [{0}]", request.JobId)) (fun exn -> log.Error(String.Format("Failed Job [{0}]", request.JobId), exn))
API Callcatch exceptions as Choice2Of2
FSharpxgithub.com/fsprojects/fsharpx/blob/master/src/FSharpx.Core/
ComputationExpressions/Monad.fs
![Page 37: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/37.jpg)
type FrequencyResponseCalculationRequest = { Interval: Interval.Time.T InitialState: FRUnitState ContractParameters: Line.Time.T<ContractParameters> Instruction: Line.Time.T<Instruction> Mel: Line.Time.T<float<MW>> Sel: Line.Time.T<float<MW>> AdjustedPN: Line.Time.T<float<MW>> ActualFrequencies: Line.Time.T<float<Hz>> TargetFrequencies: Line.Time.T<float<Hz>> MarketPrices: Line.Time.T<float<``£``/(MW h)>> }
FR Calculation Request
ubiquitous language
![Page 38: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/38.jpg)
[<Measure>] type min[<Measure>] type hh[<Measure>] type h[<Measure>] type MW!type Interval<'t> = 't * 'ttype Point<'x,'y> = 'x * 'ytype Segment<'x,'y> = Point<'x,'y> * Point<'x,'y>type Line<'x,'y> = Segment<'x,'y> list
Ubiquitous Language
all missing concepts from C# solution
units of measure
![Page 39: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/39.jpg)
module Segment =! type T<'x,'y> = | Instantaneous of Point.T<'x,'y> | Discrete of IntervalType.T * Interval.T<'x> * 'y | Continuous of Point.T<'x,'y> * Point.T<'x,'y>
Ubiquitous Language (Revised)
segment between two data points
segment is an event
segment holds a value over an interval
![Page 40: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/40.jpg)
module Units =! [<AutoOpen>] module UnitNames = /// a unit of time [<Measure>] type minute /// a unit of time [<Measure>] type halfhour /// a unit of time [<Measure>] type hour /// a unit of active power [<Measure>] type megawatt /// a unit of energy [<Measure>] type poundssterling /// a unit of frequency [<Measure>] type hertz [<AutoOpen>] module UnitSymbols = /// a synonym for halfhour, a unit of time [<Measure>] type min = minute /// a synonym for halfhour, a unit of time [<Measure>] type hh = halfhour /// a synonym for hour, a unit of time [<Measure>] type h = hour /// a synonym for megawatt, a unit of power [<Measure>] type MW = megawatt /// a synonym for pounds sterling, a unit of currency [<Measure>] type ``£`` = poundssterling /// a synonym for hertz, a unit of frequency [<Measure>] type Hz = hertz
Units of Measure
https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/SI.fs
![Page 41: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/41.jpg)
// Conversion constants let minutePerHalfhour = 30.0<min>/1.0<hh> let minutePerHour = 60.0<min>/1.0<h> let halfhourPerMinute = 1.0<hh>/30.0<min> let halfhourPerHour = 2.0<hh>/1.0<h> let hourPerMinute = 1.0<h>/60.0<min> let hourPerHalfhour = 1.0<h>/2.0<hh> module Minute = let toHalfhour (a:float<min>) = a * halfhourPerMinute let toHour (a:float<min>) = a * hourPerMinute let inline lift a = LanguagePrimitives.FloatWithMeasure<min>(float a) let liftTimeSpan (t:TimeSpan) = lift t.TotalMinutes
Units of Measure
![Page 42: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/42.jpg)
let run interval initialState parameters actualFrequencies targetFrequencies marketPrices pdtmLine = let deloadLine = DeloadLineCalculation.run … let holdingPayments = holdingPayments … let referencePrices = ReferencePriceCalculation.run … responseEnergyPayments …
Contract Evaluationtop secret
… but it involves a fold
![Page 43: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/43.jpg)
Testing// Straight forward implementation!let rec reverse = function | [] -> [] | x::xs -> reverse xs @ [x] !// Efficient implementation!let rec revAcc xs acc = match xs with | [] -> acc | h::t -> revAcc t (h::acc)!let rev xs = match xs with | [] -> xs | [_] -> xs | h1::h2::t -> revAcc t [h2;h1]!// Generate random tests to see if they behave the same!Check.Quick(fun (xs:int list) -> reverse xs = rev xs)
github.com/fsharp/FsCheck
![Page 44: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/44.jpg)
Testing
open NUnit.Frameworkopen FsUnit![<TestFixture; Category("Unit")>]type ``When I run the deload line calculation`` () =! [<Test>] member x.``with empty MEL line and empty PN line then the deload line is correct`` () = let melLine = Line.empty let pnLine = Line.empty let actual = DeloadLineCalculation.run melLine pnLine let expected : Line.Time.T<float<MW>> = Line.empty actual |> should equal expected
github.com/fsharp/FsUnit
structural equality for free
nice namesopen NUnit.Frameworkopen FsUnit![<TestFixture; Category("Unit")>]type ``When I run the deload line calculation`` () =! [<Test>] member x.``with empty MEL line and empty PN line then the deload line is correct`` () = let melLine = Line.empty let pnLine = Line.empty let actual = DeloadLineCalculation.run melLine pnLine let expected : Line.Time.T<float<MW>> = Line.empty actual |> should equal expected
![Page 45: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/45.jpg)
![Page 46: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/46.jpg)
Two Implementations of the Same ApplicationLi
nes
of C
ode
0
100000
200000
300000
400000
Braces Blanks Null Checks Comments Useful Code App Code Test Code Total Code
30,801
9,359
21,442
16,667
487
15
3,630
643
348,430
42,864
305,566
163,276
53,270
3,01129,080
56,929
C# F#
![Page 47: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/47.jpg)
… things aren’t looking good for the old way of doing things
![Page 48: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/48.jpg)
Logging LOC
![Page 49: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/49.jpg)
Exception Handling LOC
![Page 50: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/50.jpg)
Test Code Ratio
![Page 51: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/51.jpg)
Performance
![Page 52: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/52.jpg)
… and finally say yes to NOOO
![Page 53: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/53.jpg)
Manifesto for Not Only Object-Oriented Development!We are uncovering better ways of developing software by doing it and helping others do it. Through this work we have come to value: !• Functions and Types over classes • Purity over mutability • Composition over inheritance • Higher-order functions over method dispatch • Options over nulls !That is, while there is value in the items on the right (except for nulls), we value the items on the left more.
notonlyoo.org
over
0b100,000,000
signatories!
![Page 54: Time for Functions](https://reader034.vdocuments.mx/reader034/viewer/2022052315/547c8159b379597b2b8b4feb/html5/thumbnails/54.jpg)
@simontcousins
simontylercousins.net
www.slideshare.net/simontcousins/time-for-functions