$30 off During Our Annual Pro Sale. View Details »

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. Robert Pickering

    View Slide

  2. Programming is about data
    You have been fooled into
    thinking it’s about APIs

    View Slide

  3. Functional Programming is
    not about programming
    without state

    View Slide

  4. It is about succinctly describe the
    transformation between one
    state another, in a way that is
    composable.

    View Slide

  5. Purity: only relying on data that’s
    passed as a parameter

    View Slide

  6. The devil is in the detail
    Programming in the large
    Programming in the small
    versus

    View Slide

  7. The Software Crisis
    Complexity
    Conformity
    Changeability
    Invisibility

    View Slide

  8. Essential Complexity
    Accidental Complexity
    versus

    View Slide

  9. Have you tried
    turning it off and
    on again?

    View Slide

  10. 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 …

    View Slide

  11. Long running app statement oriented style
    Process Event
    Environment

    View Slide

  12. Long running app expression oriented style
    Process Event
    Environment

    View Slide

  13. Expressions vs Statements
    An expression is something
    that evaluates to value
    A statement is
    everything else

    View Slide

  14. If in a statement oriented language
    string firstArg = null;
    if (args.Length > 0)
    {
    firstArg = args[0];
    }

    View Slide

  15. If in a expression oriented language
    let firstArg =
    if argv.Length > 1 then
    argv.[0]
    else
    ""

    View Slide

  16. What about the ternary operator?
    string firstArg =
    (args.Length > 0)
    ? args[0] : "";

    View Slide

  17. There are limits to the ternary operator
    let firstArg =
    if argv.Length > 1 then
    let argString = argv.[0]
    argString.Trim()
    else
    ""

    View Slide

  18. If statement with two values
    string firstArg = null;
    string secondArg = null;
    if (args.Length > 1)
    {
    firstArg = args[0];
    secondArg = args[1];
    }

    View Slide

  19. If expression with two values
    let firstArg, secondArg =
    if argv.Length > 1 then
    argv.[0], argv.[1]
    else
    "", ""

    View Slide

  20. Data gets trapped
    inside statements and
    needs to be pushed to
    a specific location in
    the outside world

    View Slide

  21. 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];
    }
    // ...
    }

    View Slide

  22. Lock expression/function
    let sync = new obj()
    let items = [| "1"; "2"; "3"|]
    let DoItemEx5 index =
    let myDataPoint =
    lock sync (fun () -> items.[index])
    // ...

    View Slide

  23. 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();
    }
    }

    View Slide

  24. use binding
    use conn = new SqlConnection()
    conn.Open();
    use cmd =
    conn.CreateCommand(CommandText = "select field from table")
    let myDataPoint =
    cmd.ExecuteScalar() :?> string

    View Slide

  25. Statement oriented
    programming emphasizes
    reading and write, and so
    creates coupling with the
    points being read
    from/written to

    View Slide

  26. Expression oriented
    programming emphasizes the
    calculations and transformation
    between one value and
    another.

    View Slide

  27. 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++

    View Slide

  28. 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

    View Slide

  29. The inconvenience of parameters
    let bigFunc thing1 otherThing thirdItem moreStuff
    gettingBordNow engoughAlready =
    // ...

    View Slide

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

    View Slide

  31. 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));
    }
    }

    View Slide

  32. 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

    View Slide

  33. 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!

    View Slide

  34. The problem with fields

    View Slide

  35. public class Stuff { }
    public interface IRepository
    {
    Stuff[] GetStuff();
    }
    public class MockableClass
    {
    IRepository _repository;
    public MockableClass(IRepository repository)
    {
    _repository = repository;
    }
    // ...
    }
    A word on mocking …

    View Slide

  36. 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

    View Slide

  37. .NET Type System
    The .NET type system guarantees that a
    reference either points to valid instance
    or null

    View Slide

  38. NullReferenceException

    View Slide

  39. 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)

    View Slide

  40. Tuples
    let tuple = "first", 2
    let first, two = tuple

    View Slide

  41. Record Types
    type Person =
    { FirstName: string;
    LastName: string; }

    View Slide

  42. Record Types
    let person =
    { FirstName = "Robert";
    LastName = "Pickering"; }
    let person' =
    { person with
    LastName = "Townson"; }

    View Slide

  43. Union Types
    type Choice<'a, 'b> =
    | Choice1 of 'a
    | Choice2 of 'b

    View Slide

  44. Union Types
    let aChoice = Choice1 "made"
    let whichChoice =
    match aChoice with
    | Choice1 value -> sprintf "Choice1 %A" value
    | Choice2 value -> sprintf "Choice2 %A" value

    View Slide

  45. 45
    // pipe forward
    let (|>) x f = f x
    Pipe Forward

    View Slide

  46. 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)

    View Slide

  47. 47
    // compose
    let (>>) f g x = g (f x)
    Compose
    let compose func1 func2 value =
    let result = func1 value
    func2 result

    View Slide

  48. 48
    let length (s: string) =
    s.Length
    let makeString i =
    new String('a', i)
    let combinedFunction =
    length >> makeString
    combinedFunction "1234" // result "aaaa"
    Compose

    View Slide

  49. An LOB app using composition

    View Slide

  50. type Request =
    { name:string;
    email:string }
    A type to hold the data

    View Slide

  51. A type to represent success or failure
    // the two-track type
    type Result<'TSuccess,'TFailure> =
    | Success of 'TSuccess
    | Failure of 'TFailure

    View Slide

  52. 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

    View Slide

  53. bind
    let bind inputFunction inputValue =
    match inputValue with
    | Success s -> inputFunction s
    | Failure f -> Failure f

    View Slide

  54. 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

    View Slide

  55. 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)

    View Slide

  56. plus
    let combineFailures v1 v2 =
    let addSuccess r1 r2 = r1 // return first
    let addFailure s1 s2 = s1 + "; " + s2 // concat
    plus addSuccess addFailure v1 v2

    View Slide

  57. plus
    let combinedValidation =
    validate1
    |> combineFailures validate2
    |> combineFailures validate3

    View Slide

  58. Normalizing the data
    let canonicalizeEmail input =
    { input with
    email = input.email.Trim().ToLower() }

    View Slide

  59. map
    let map f inputValue =
    match inputValue with
    | Success s -> Success (f s)
    | Failure f -> Failure f

    View Slide

  60. map
    let usecase =
    combinedValidation
    >> map canonicalizeEmail

    View Slide

  61. let updateDatabase input =
    () // dummy dead-end function for now
    Updating the database

    View Slide

  62. tee
    let tee f x =
    f x; x

    View Slide

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

    View Slide

  64. let updateDatebaseStep request =
    tryCatch
    (tee updateDatabase)
    (fun ex -> ex.Message)
    request
    Converted db function

    View Slide

  65. Completed Usecase
    let usecase =
    combinedValidation
    >> map canonicalizeEmail
    >> bind updateDatebaseStep

    View Slide

  66. 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 …

    View Slide

  67. With a big thanks to …
    Railway oriented programming
    http://fsharpforfunandprofit.com/posts/recipe-part2/

    View Slide

  68. Further Reading

    View Slide

  69. View Slide

  70. Questions?

    View Slide