Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Expression Oriented Programming with F#

Expression Oriented Programming with F#

This talk take a deep look at some of the basics of programming: expressions, statements, scope and how we represent data. We’ll look at these concepts to through the lens of a functional programmer to see how this, contrasts to the way an Object Oriented programmer might do things. We’ll look at how these ideas effect application architecture both “in the large” and “in the small”. “In the large” meaning overall system design and “in the small” meaning design of individual classes, methods and functions.Once we’ve examined the basics, we’ll go on to take a look at one of the biggest problems faced by software developers: complexity. We’ll see how complexity can either be accidental or essential. We’ll see how expression oriented programming can help us avoid accidental complexity and help us crawl out of the tar pit.The examples will see will use F#, but the ideas will be applicable to any language that encourages the use of expressions rather than statements.

Robert Pickering

December 09, 2013
Tweet

More Decks by Robert Pickering

Other Decks in Programming

Transcript

  1. The devil is in the detail Programming in the large

    Programming in the small versus
  2. Command-line App File File Service/Daemon Network I/O Network I/O GUI

    App User Input Image Data The only three apps you’ll ever write …
  3. If in a statement oriented language string firstArg = null;

    if (args.Length > 0) { firstArg = args[0]; }
  4. If in a expression oriented language let firstArg = if

    argv.Length > 1 then argv.[0] else ""
  5. There are limits to the ternary operator let firstArg =

    if argv.Length > 1 then let argString = argv.[0] argString.Trim() else ""
  6. If statement with two values string firstArg = null; string

    secondArg = null; if (args.Length > 1) { firstArg = args[0]; secondArg = args[1]; }
  7. If expression with two values let firstArg, secondArg = if

    argv.Length > 1 then argv.[0], argv.[1] else "", ""
  8. Data gets trapped inside statements and needs to be pushed

    to a specific location in the outside world
  9. Lock statement static object sync = new object(); static string[]

    items = new string[] { "1", "2", "3" }; static void DoItemEx5(int index) { string myDataPoint = null; lock (sync) { myDataPoint = items[index]; } // ... }
  10. Lock expression/function let sync = new obj() let items =

    [| "1"; "2"; "3"|] let DoItemEx5 index = let myDataPoint = lock sync (fun () -> items.[index]) // ...
  11. Using statement string myDataPoint = null; using (var conn =

    new SqlConnection()) { conn.Open(); using (var cmd = conn.CreateCommand()) { cmd.CommandText = "select field from table"; myDataPoint = (string)cmd.ExecuteScalar(); } }
  12. use binding use conn = new SqlConnection() conn.Open(); use cmd

    = conn.CreateCommand(CommandText = "select field from table") let myDataPoint = cmd.ExecuteScalar() :?> string
  13. Statement oriented programming emphasizes reading and write, and so creates

    coupling with the points being read from/written to
  14. The process of refactoring towards purity generally involves disentangling computation

    from the environment it operates in, which almost invariably means more parameter passing John Carmack Functional Programming in C++
  15. Ways to get data into a function 1. Read the

    data from somewhere 2. Have the caller pass the data to you as a parameter
  16. Nesting functions reduce need for parameters let bigFunc' thing1 otherThing

    thirdItem moreStuff gettingBordNow engoughAlready = let doSomething1 index = (thing1 + index) - thirdItem let doSomething2 index = (thing1 + otherThing) * moreStuff doSomething1 1 |> doSomething2 // ...
  17. class BadClass { int thing1; int otherThing; int thirdItem; int

    moreStuff; int gettingBordNow; int engoughAlready; int DoSomething1(int index) { return (thing1 + index) - thirdItem; } int DoSomething2(int index) { return (thing1 + otherThing) * moreStuff; } public int BigFunc(int thing1, int otherThing, int thirdItem, int moreStuff, int gettingBordNow, int engoughAlready) { // ... unpack fields ... return DoSomething2(DoSomething1(1)); } }
  18. Nested functions offer better protection than private fields let hardWorkingFunc

    environment = let buffer = Array.zeroCreate 255: int[] let doWork1 parameter = // do some more work with buffer () let doWork2 parameter = for x in buffer do doWork1 x buffer
  19. class HardWorkingClass { int[] buffer = new int[255]; private void

    DoWork1(int parameter) { // do some more work with buffer } private void DoWork2(int parameter) { foreach (var x in buffer) { DoWork1(x); } } public int[] HardWorkingFunction() { DoWork2(1); return buffer; } } // game over!
  20. public class Stuff { } public interface IRepository { Stuff[]

    GetStuff(); } public class MockableClass { IRepository _repository; public MockableClass(IRepository repository) { _repository = repository; } // ... } A word on mocking …
  21. Testability: Take a dependencies on anything other than the data

    pasted to you, and the test will need to ensure those dependencies are correctly initialized
  22. .NET Type System The .NET type system guarantees that a

    reference either points to valid instance or null
  23. F# Type System The F# type system guarantees that a

    reference either points to valid instance or null (or at least tries very hard to)
  24. Record Types let person = { FirstName = "Robert"; LastName

    = "Pickering"; } let person' = { person with LastName = "Townson"; }
  25. Union Types let aChoice = Choice1 "made" let whichChoice =

    match aChoice with | Choice1 value -> sprintf "Choice1 %A" value | Choice2 value -> sprintf "Choice2 %A" value
  26. 46 Pipe Forward let items = [ 1 .. 10

    ] let squares = Seq.map (fun x -> x * x) (Seq.filter (fun x -> x > 3) items) let squares' = items |> Seq.filter (fun x -> x > 3) |> Seq.map (fun x -> x * x)
  27. 47 // compose let (>>) f g x = g

    (f x) Compose let compose func1 func2 value = let result = func1 value func2 result
  28. 48 let length (s: string) = s.Length let makeString i

    = new String('a', i) let combinedFunction = length >> makeString combinedFunction "1234" // result "aaaa" Compose
  29. A type to represent success or failure // the two-track

    type type Result<'TSuccess,'TFailure> = | Success of 'TSuccess | Failure of 'TFailure
  30. let validate1 input = if input.name = "" then Failure

    "Name must not be blank" else Success input let validate2 input = if input.name.Length > 50 then Failure "Name must not be longer than 50 chars" else Success input let validate3 input = if input.email = "" then Failure "Email must not be blank" else Success input Validation functions
  31. bind let bind inputFunction inputValue = match inputValue with |

    Success s -> inputFunction s | Failure f -> Failure f
  32. bind let combindedValidate = let validate1' = bind validate1 let

    validate2' = bind validate2 let validate3' = bind validate3 validate1' >> validate2' >> validate3' let altCombindedValidate = bind validate1 >> bind validate2 >> bind validate3
  33. plus let plus addSuccess addFailure switch1 switch2 x = match

    (switch1 x),(switch2 x) with | Failure f1,Success _ -> Failure f1 | Success _ ,Failure f2 -> Failure f2 | Success s1,Success s2 -> Success (addSuccess s1 s2) | Failure f1,Failure f2 -> Failure (addFailure f1 f2)
  34. plus let combineFailures v1 v2 = let addSuccess r1 r2

    = r1 // return first let addFailure s1 s2 = s1 + "; " + s2 // concat plus addSuccess addFailure v1 v2
  35. map let map f inputValue = match inputValue with |

    Success s -> Success (f s) | Failure f -> Failure f
  36. let tryCatch f exnHandler x = try Success(f x) with

    | ex -> Failure(exnHandler ex) tryCatch
  37. let mainCompile (argv,bannerAlreadyPrinted,exiter:Exiter,createErrorLogger) = // Don's note: "GC of intermediate

    data is really, really important here" main1 (argv,bannerAlreadyPrinted,exiter,createErrorLogger) |> main2 |> main2b |> main2c |> main3 |> main4 In the real world …