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

Railway-Oriented Programming in PHP

Railway-Oriented Programming in PHP

Whether it's lambda functions (closures), immutable objects in PSR-7, or the pipe operator RFC (https://wiki.php.net/rfc/pipe-operator …), functional programming has been the in-vogue influence on PHP in the last few years. Can we harness it to create code that's far more reusable than objects are?

In this talk, Stuart will introduce you to railway-oriented programming (ROP), a functional approach to reusable composition from Scott Wlaschin. Together, we'll build up an implementation of ROP in PHP, and at the end, we'll debate the question: would we use the end result in anger?

Delivered at BrumPHP on 28th March, 2018.

Stuart Herbert

March 28, 2018
Tweet

More Decks by Stuart Herbert

Other Decks in Programming

Transcript

  1. Industry veteran: architect, engineer, leader, manager, mentor F/OSS contributor since

    1994 Talking and writing about PHP since 2004 Chief Software Archaeologist Building Quality @GanbaroDigital About Stuart
  2. @GanbaroDigital RRR Development • Reusable • Reason about it •

    Robust ... without sacrificing performance!
  3. @GanbaroDigital “A change in one of the things we can’t

    see can break the one thing we can see.
  4. @GanbaroDigital “ One thing goes in, a new thing comes

    out. The thing that went in remains unchanged.
  5. @GanbaroDigital public function validateEmail($email) { if (preg_match(..., $email)) { if

    (not_blacklisted($email)) { if (mailbox_exists($email)) { return $email; } } } throw new Exception(...); }
  6. @GanbaroDigital public function validateEmail($email) { if (preg_match(..., $email)) { if

    (not_blacklisted($email)) { if (mailbox_exists($email)) { return $email; } } } throw new Exception(...); }
  7. @GanbaroDigital All Joking Aside ... • Nested ‘if’ adds testing

    complexity • Becomes fragile over time, as rules change
  8. @GanbaroDigital All Joking Aside ... • Nested ‘if’ adds testing

    complexity • Becomes fragile over time, as rules change
  9. @GanbaroDigital public function validateEmail($email) { if (preg_match(..., $email)) { if

    (not_blacklisted($email)) { if (mailbox_exists($email)) { return $email; } } } throw new Exception(...); }
  10. @GanbaroDigital public function validateEmail($email) { if (preg_match(..., $email)) { if

    (not_blacklisted($email)) { if (mailbox_exists($email)) { return $email; } } } throw new Exception(...); }
  11. @GanbaroDigital “ Because there are no side-effects we can safely

    run the whole pipeline where that makes sense.
  12. @GanbaroDigital ROP Function Actions • Apply logic to input data

    • Produce new output data • Input data remains unchanged
  13. @GanbaroDigital ROP Function Composition • Maybe we want to short-circuit

    on error • Maybe we want to aggregate errors • This is policy - it belongs in the calling code
  14. @GanbaroDigital We can emulate the flow logic of Monads. We

    can’t emulate the type system that makes them practical.
  15. @GanbaroDigital Starting Requirements • Data input, optional error input •

    Data output, error output • Input data treated as immutable
  16. @GanbaroDigital The Simple Approach • Arg 1 - always the

    data to transform • Arg 2 - optional failure data • Returns 2-element array
  17. @GanbaroDigital Why Return An Array? • Arrays outperform objects in

    PHP • No need to define any classes, or pull in any third-party packages
  18. @GanbaroDigital Use a function that returns a function to make

    things composable AND avoid hard-coding parameters.
  19. @GanbaroDigital Rule of thumb: any function that takes 1 parameter

    and isn’t built from composed functions assume it has something hard-coded in there until you prove otherwise!
  20. @GanbaroDigital Partial Function Builder • Reduces amount of code to

    write • Requires main data to be first parameter of the wrapped function • Convenience over runtime performance • No type-safety
  21. @GanbaroDigital Partial Function Builder • Reduces amount of code to

    write • Requires main data to be first parameter of the wrapped function • Convenience over runtime performance • No type-safety
  22. @GanbaroDigital Partial Function Builder • Reduces amount of code to

    write • Requires main data to be first parameter of the wrapped function • Convenience over runtime performance • No type-safety
  23. @GanbaroDigital Partial Function Builder • Reduces amount of code to

    write • Requires main data to be first parameter of the wrapped function • Convenience over runtime performance • No type-safety
  24. @GanbaroDigital Requirements • Applied after all other costs • Show

    the discount to make the customer feel the value
  25. @GanbaroDigital Requirements • Applied after all other costs • Show

    the discount to make the customer feel the value
  26. @GanbaroDigital Scott talked about ROP as an approach to error

    handling. We can’t put it off any longer :-)
  27. @GanbaroDigital Option 1 Consequences • We don’t need $failure at

    all (not ROP!) • Generic exceptions are a huge time sink when investigating faults • Specific exceptions lead to large try/catch blocks* • try/catch blocks are part of our change surface area - Pryamid of Doom / fragile
  28. @GanbaroDigital Option 1 Consequences • We don’t need $failure at

    all (not ROP!) • Generic exceptions are a huge time sink when investigating faults • Specific exceptions lead to large try/catch blocks* • try/catch blocks are part of our change surface area - Pryamid of Doom / fragile
  29. @GanbaroDigital Option 1 Consequences • We don’t need $failure at

    all (not ROP!) • Generic exceptions are a huge time sink when investigating faults • Specific exceptions lead to large try/catch blocks* • try/catch blocks are part of our change surface area - Pryamid of Doom / fragile
  30. @GanbaroDigital Option 1 Consequences • We don’t need $failure at

    all (not ROP!) • Generic exceptions are a huge time sink when investigating faults • Specific exceptions lead to large try/catch blocks* • try/catch blocks are part of our change surface area - Pryamid of Doom / fragile
  31. @GanbaroDigital Option 2 Consequences • Everything has to agree on

    what $failure is, and how to use it • Still need to return *something* as main data out • Someone needs to remember to check $failure
  32. @GanbaroDigital Option 2 Consequences • Everything has to agree on

    what $failure is, and how to use it • Still need to return *something* as main data out • Someone needs to remember to check $failure
  33. @GanbaroDigital Option 2 Consequences • Everything has to agree on

    what $failure is, and how to use it • Still need to return *something* as main data out • Someone needs to remember to check $failure
  34. @GanbaroDigital Option 1 is effort and fragile. (And it’s the

    current way) Option 2 requires 100% accuracy from humans.
  35. @GanbaroDigital Option 3 Consequences • Everything still has to agree

    on what $failure is, and how to use it • 2nd parameter is non-optional callback • Caller decides how to handle errors • Caller can throw exceptions if preferred
  36. @GanbaroDigital Option 3 Consequences • Everything still has to agree

    on what $failure is, and how to use it • 2nd parameter is non-optional callback • Caller decides how to handle errors • Caller can throw exceptions if preferred
  37. @GanbaroDigital Option 3 Consequences • Everything still has to agree

    on what $failure is, and how to use it • 2nd parameter is non-optional callback • Caller decides how to handle errors • Caller can throw exceptions if preferred
  38. @GanbaroDigital Option 3 Consequences • Everything still has to agree

    on what $failure is, and how to use it • 2nd parameter is non-optional callback • Caller decides how to handle errors • Caller can throw exceptions if preferred