Top Banner
Time for Functions @simontcousins
54

Time for Functions

Nov 29, 2014

Download

Technology

simontcousins

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.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Time for Functions

Time for Functions@simontcousins

Page 2: Time for Functions
Page 3: Time for Functions
Page 4: Time for Functions

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

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

• 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 7: Time for Functions

but…and this is a really big but

http://xkcd.com/

Page 8: Time for Functions

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

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

• 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
Page 12: Time for Functions

• 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

ww

w.statnett.no

stay at 50Hz

by adjusting

these

THE ENERGY SECTOR

{to make this zero

Page 14: Time for Functions

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

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

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
Page 18: Time for Functions

Elegant

beautiful

simple

efficient

functional

Page 19: Time for Functions

Not Elegant

“… but hey, it’s object-oriented!”

Page 20: Time for Functions

• 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

me preparing

to mutate some state

Page 22: Time for Functions

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

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
Page 25: Time for Functions

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

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

Adoption: Managers

?

Page 28: Time for Functions

Approachexploratory REPL driven

DRYer repeatedly re-factor

test driven documented development

Page 29: Time for Functions

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

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

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

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

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

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

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

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

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

[<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

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

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

// 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

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

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

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
Page 46: Time for Functions

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

… things aren’t looking good for the old way of doing things

Page 48: Time for Functions

Logging LOC

Page 49: Time for Functions

Exception Handling LOC

Page 50: Time for Functions

Test Code Ratio

Page 51: Time for Functions

Performance

Page 52: Time for Functions

… and finally say yes to NOOO

Page 53: Time for Functions

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

@simontcousins

simontylercousins.net

www.slideshare.net/simontcousins/time-for-functions