Save 37% off PRO during our Black Friday Sale! »

Functional Object-Oriented Programming

Functional Object-Oriented Programming

One of the only certainties of software development is change. This change can often be painful because it means that we have to refactor or rewrite large quantities of our application. In order to ease the pain, programmers have developed a number of rules and techniques and codified them as "Best Practices". This is especially true in Object Oriented Programming.

Using real world examples we will discuss the strengths and weaknesses of OOP and explore the strengths and benefits of applying functional programming to our OOP code.

06f8b41980eb4c577fa40c41d5030c19?s=128

Chris Keathley

July 18, 2016
Tweet

Transcript

  1. FUNCTIONAL OBJECT-ORIENTED PROGRAMMING Chris Keathley / @ChrisKeathley / keathley@carbonfive.com

  2. What this talk isn’t about

  3. “OO IS EVIL!!!” - People on the internet

  4. “OO IS EVIL!!!” - People on the internet Kthnxbye

  5. OO vs FP

  6. I was trained in this

  7. C

  8. int main(int argc, char** argv) { mpc_parser_t* Number = mpc_new("number");

    mpc_parser_t* Symbol = mpc_new("symbol"); mpc_parser_t* Sexpr = mpc_new("sexpr"); mpc_parser_t* Qexpr = mpc_new("qexpr"); mpc_parser_t* Expr = mpc_new("expr"); mpc_parser_t* Lispy = mpc_new("lispy"); mpca_lang(MPC_LANG_DEFAULT, " \ number : /-?[0-9]+/ ; \ symbol : '+' | '-' | '*' | '/' ; \ sexpr : '(' <expr>* ')' ; \ qexpr : '{' <expr>* '}' ; \ expr : <number> | <symbol> | <sexpr> ; \ lispy : /^/ <expr>* /$/ ; \ ", Number, Symbol, Sexpr, Qexpr, Expr, Lispy); puts("Crisp Version 0.0.0.1"); puts("Press Ctrl+c to Exit\n"); while (1) { char* input = readline("crisp> "); add_history(input); mpc_result_t r; if (mpc_parse("<stdin>", input, Lispy, &r)) { lval* x = lval_eval(lval_read(r.output)); lval_println(x); lval_del(x); mpc_ast_delete(r.output); } else { mpc_err_print(r.error); mpc_err_delete(r.error);
  9. int main(int argc, char** argv) { mpc_parser_t* Number = mpc_new("number");

    mpc_parser_t* Symbol = mpc_new("symbol"); mpc_parser_t* Sexpr = mpc_new("sexpr"); mpc_parser_t* Qexpr = mpc_new("qexpr"); mpc_parser_t* Expr = mpc_new("expr"); mpc_parser_t* Lispy = mpc_new("lispy"); mpca_lang(MPC_LANG_DEFAULT, " \ number : /-?[0-9]+/ ; \ symbol : '+' | '-' | '*' | '/' ; \ sexpr : '(' <expr>* ')' ; \ qexpr : '{' <expr>* '}' ; \ expr : <number> | <symbol> | <sexpr> ; \ lispy : /^/ <expr>* /$/ ; \ ", Number, Symbol, Sexpr, Qexpr, Expr, Lispy); puts("Crisp Version 0.0.0.1"); puts("Press Ctrl+c to Exit\n"); while (1) { char* input = readline("crisp> "); add_history(input); mpc_result_t r; if (mpc_parse("<stdin>", input, Lispy, &r)) { lval* x = lval_eval(lval_read(r.output)); lval_println(x); lval_del(x); mpc_ast_delete(r.output); } else { mpc_err_print(r.error); mpc_err_delete(r.error); wtfbbq
  10. Java

  11. What is Object-Oriented Programming?

  12. OBJECT ORIENTED LANGUAGES HAVE… ▸ Behavior + Data (encapsulation) ▸

    Classes ▸ Objects (instances of classes) ▸ Inheritance ▸ Methods ▸ Polymorphism
  13. OBJECT ORIENTED PROGRAMMING IMPLIES… ▸ State ▸ Mutability ▸ Encapsulation

    ▸ Domain modeling ▸ Message passing
  14. ANIMALS

  15. ANIMALS BIRDS

  16. ANIMALS BIRDS DOGS

  17. How do we use Oop?

  18. I read lots of books

  19. THE CLASSICS ▸ Design Patterns (Gang of 4) ▸ Growing

    Object-Oriented Software with Tests ▸ Practical Object-Oriented Design in Ruby ▸ Domain Driven Design ▸ Patterns of Enterprise Application Architecture
  20. I Learned best Practices

  21. BEST PRACTICES ▸ SOLID ▸ DRY ▸ Law of Demeter

    ▸ Small Methods ▸ Design by Contract ▸ TDD ▸ BDD ▸ Commands and Queries ▸ Tell don’t ask ▸ Separation of Concerns
  22. Un-enforced Best Practices will eventually be violated

  23. Complexity “CONSISTING OF MANY DIFFERENT AND CONNECTED PARTS”

  24. Simple “COMPOSED OF A SINGLE ELEMENT.”

  25. Decoupling

  26. Complexity is the death of productivity

  27. “the first 90% of all software products is the MVP,

    the other 90% is maintenance” - Greg O.
  28. OOP is complex By Nature

  29. Encapsulation STATE BEHAVIOR

  30. Inheritance ANIMALS BIRDS

  31. Inheritance ANIMALS BIRDS

  32. State FOO BAR BAZ

  33. State FOO BAR BAZ ME

  34. State FOO BAR BAZ ME Foo

  35. State FOO BAR BAZ ME Foo Bar

  36. State FOO BAR BAZ ME Foo Baz Bar

  37. Methods FOO STATE PRIVATE METHOD PRIVATE METHOD

  38. I combat complexity with Functional Programming

  39. functional Programming pure functions immutable data isolate side-effects Compose pipelines

    of functions
  40. functional Programming pure functions immutable data isolate side-effects Compose pipelines

    of functions
  41. Pure Functions (+2) 3 5 Always return the same result

    given the same arguments
  42. Pure Functions Have no side effects FS DB +

  43. Pure Functions # Pure def add(x, y) x + y

    end # Impure def user_input_add(x) print "Enter a number: " num = gets puts x + num.to_i end
  44. functional Programming pure functions immutable data isolate side-effects Compose pipelines

    of functions
  45. Immutability test "users can be fed" do user = User.new

    assert user.hungry? == true user.feed(Hamburger.new) assert user.hungry? == false end class User def initialize(stomach: []) @stomach = stomach end def feed(item) @stomach << item end end
  46. Immutability test "users can be fed" do user = User.new

    assert user.hungry? == true fed_user = user.feed(Hamburger.new) assert fed_user.hungry? == false end class User def initialize(stomach: []) @stomach = stomach end def feed(item) @stomach << item end end
  47. Immutability test "users can be fed" do user = User.new

    assert user.hungry? == true fed_user = user.feed(Hamburger.new) assert fed_user.hungry? == false end class User def initialize(stomach: []) @stomach = stomach end def feed(item) User.new(stomach: @stomach + [item]) end end
  48. Immutability USER WALLABY .feed .feed

  49. functional Programming pure functions immutable data isolate side-effects Compose pipelines

    of functions
  50. Isolate side-effects FILE DB HTTP

  51. Isolate side-effects FILE DB HTTP

  52. Isolate side-effects FILE DB HTTP

  53. functional Programming pure functions immutable data isolate side-effects Compose pipelines

    of functions
  54. composing side-effects web_request

  55. composing side-effects web_request |> transform_request_into_query

  56. composing side-effects web_request |> transform_request_into_query |> execute_query

  57. composing side-effects web_request |> transform_request_into_query |> execute_query |> transform_query_result

  58. composing side-effects web_request |> transform_request_into_query |> execute_query |> transform_query_result |>

    return_result_to_user
  59. composing side-effects web_request |> transform_request_into_query |> execute_query |> transform_query_result |>

    return_result_to_user Data
  60. composing side-effects web_request |> transform_request_into_query |> execute_query |> transform_query_result |>

    return_result_to_user Pure Data
  61. composing side-effects web_request |> transform_request_into_query |> execute_query |> transform_query_result |>

    return_result_to_user Pure Impure Data
  62. Example

  63. class NewTicketService def self.create_ticket(ticket_params) ticket = Ticket.create(ticket_params) TicketNotifier.new(TicketAlertMailer.new(ticket)).send_alert end end

  64. class NewTicketService def self.create_ticket(ticket_params) ticket = Ticket.create(ticket_params) TicketNotifier.new(TicketAlertMailer.new(ticket)).send_alert end end

    class TicketNotifier def initialize(notifier) @notifier = notifier end def send_alert OnCallStaff.all.each do |user| @notifier.notify(user) end end end
  65. class NewTicketService def self.create_ticket(ticket_params) ticket = Ticket.create(ticket_params) TicketNotifier.new(TicketAlertMailer.new(ticket)).send_alert end end

    class TicketNotifier def initialize(notifier) @notifier = notifier end def send_alert OnCallStaff.all.each do |user| @notifier.notify(user) end end end class TicketAlertMailer def initialize(ticket) @ticket end def self.notify(user) email!(user.email, @ticket.id, @ticket.details) end end
  66. Separate arrangement & work

  67. class NewTicketService def self.create_ticket(ticket_params) ticket = Ticket.create(ticket_params) TicketNotifier.new(TicketAlertMailer.new(ticket)).send_alert end end

    class TicketNotifier def initialize(notifier) @notifier = notifier end def send_alert OnCallStaff.all.each do |user| @notifier.notify(user) end end end class TicketAlertMailer def initialize(ticket) @ticket end def notify(user) email!(user.email, @ticket.id, @ticket.details) end end
  68. class NewTicketService def self.create_ticket(ticket_params) ticket = Ticket.create(ticket_params) TicketNotifier.new(TicketAlertMailer.new(ticket)).send_alert end end

    class TicketNotifier def initialize(notifier) @notifier = notifier end def send_alert OnCallStaff.all.each do |user| @notifier.notify(user) end end end class TicketAlertMailer def initialize(ticket) @ticket end def notify(user) email!(user.email, @ticket.id, @ticket.details) end end arrangement
  69. class NewTicketService def self.create_ticket(ticket_params) ticket = Ticket.create(ticket_params) TicketNotifier.new(TicketAlertMailer.new(ticket)).send_alert end end

    class TicketNotifier def initialize(notifier) @notifier = notifier end def send_alert OnCallStaff.all.each do |user| @notifier.notify(user) end end end class TicketAlertMailer def initialize(ticket) @ticket end def notify(user) email!(user.email, @ticket.id, @ticket.details) end end arrangement Work
  70. class NewTicketService def self.create_ticket(ticket_params) ticket = Ticket.create(ticket_params) TicketNotifier.new(TicketAlertMailer.new(ticket)).send_alert end end

    class TicketNotifier def initialize(notifier) @notifier = notifier end def send_alert OnCallStaff.all.each do |user| @notifier.notify(user) end end end class TicketAlertMailer def initialize(ticket) @ticket end def notify(user) email!(user.email, @ticket.id, @ticket.details) end end
  71. class NewTicketService def self.create_ticket(ticket_params) ticket = Ticket.create(ticket_params) OnCallStaff.all.each do |user|

    TicketNotifier.new(TicketAlertMailer.new(ticket)).send_alert(user) end end end class TicketNotifier def initialize(notifier) @notifier = notifier end def send_alert(user) @notifier.notify(user) end end class TicketAlertMailer def initialize(ticket) @ticket end def notify(user) email!(user.email, @ticket.id, @ticket.details) end end
  72. class NewTicketService def self.create_ticket(ticket_params) ticket = Ticket.create(ticket_params) OnCallStaff.all.each do |user|

    TicketAlertMailer.new(ticket).send_alert(user) end end end class TicketAlertMailer def initialize(ticket) @ticket end def notify(user) email!(user.email, @ticket.id, @ticket.details) end end
  73. class NewTicketService def self.create_ticket(ticket_params) ticket = Ticket.create(ticket_params) OnCallStaff.all.each do |user|

    TicketAlertMailer.notify(user, ticket) end end end class TicketAlertMailer def notify(user, ticket) email!(user.email, ticket.id, ticket.details) end end
  74. class NewTicketService def self.create_ticket(ticket_params) ticket = Ticket.create(ticket_params) OnCallStaff.all.each do |user|

    TicketAlertMailer.notify(user, ticket) SlackNotifier.notify(user, ticket) end end end class TicketAlertMailer def notify(user, ticket) email!(user.email, ticket.id, ticket.details) end end class SlackNotifier def self.notify(user, ticket) send_slack_message(user.slack_name, ticket.details) end end
  75. COULD I MAKE THIS AN API CALL?

  76. COULD I MAKE THIS CONCURRENT?

  77. COULD I PUT A QUEUE IN BETWEEN THESE OPERATIONS?

  78. Conclusion

  79. You don’t have to convert all of your code to

    a new language.
  80. Lets stop making problems for ourselves

  81. Think about the goals of best practices.

  82. Build simpler systems

  83. Chris Keathley / @ChrisKeathley / keathley@carbonfive.com Thanks!