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

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.

Chris Keathley

July 18, 2016
Tweet

More Decks by Chris Keathley

Other Decks in Programming

Transcript

  1. FUNCTIONAL OBJECT-ORIENTED
    PROGRAMMING
    Chris Keathley / @ChrisKeathley / [email protected]

    View Slide

  2. What this talk
    isn’t about

    View Slide

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

    View Slide

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

    View Slide

  5. OO vs FP

    View Slide

  6. I was trained in this

    View Slide

  7. C

    View Slide

  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 : '(' * ')' ; \
    qexpr : '{' * '}' ; \
    expr : | | ; \
    lispy : /^/ * /$/ ; \
    ",
    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("", 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);

    View Slide

  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 : '(' * ')' ; \
    qexpr : '{' * '}' ; \
    expr : | | ; \
    lispy : /^/ * /$/ ; \
    ",
    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("", 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

    View Slide

  10. Java

    View Slide

  11. What is Object-Oriented
    Programming?

    View Slide

  12. OBJECT ORIENTED LANGUAGES HAVE…
    ▸ Behavior + Data (encapsulation)
    ▸ Classes
    ▸ Objects (instances of classes)
    ▸ Inheritance
    ▸ Methods
    ▸ Polymorphism

    View Slide

  13. OBJECT ORIENTED PROGRAMMING IMPLIES…
    ▸ State
    ▸ Mutability
    ▸ Encapsulation
    ▸ Domain modeling
    ▸ Message passing

    View Slide

  14. ANIMALS

    View Slide

  15. ANIMALS
    BIRDS

    View Slide

  16. ANIMALS
    BIRDS DOGS

    View Slide

  17. How do we use Oop?

    View Slide

  18. I read lots of books

    View Slide

  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

    View Slide

  20. I Learned best Practices

    View Slide

  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

    View Slide

  22. Un-enforced Best
    Practices will
    eventually be violated

    View Slide

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

    View Slide

  24. Simple
    “COMPOSED OF A SINGLE ELEMENT.”

    View Slide

  25. Decoupling

    View Slide

  26. Complexity is the death
    of productivity

    View Slide

  27. “the first 90% of all
    software products is the
    MVP, the other 90% is
    maintenance”
    - Greg O.

    View Slide

  28. OOP is
    complex
    By Nature

    View Slide

  29. Encapsulation
    STATE
    BEHAVIOR

    View Slide

  30. Inheritance
    ANIMALS
    BIRDS

    View Slide

  31. Inheritance
    ANIMALS
    BIRDS

    View Slide

  32. State
    FOO BAR BAZ

    View Slide

  33. State
    FOO BAR BAZ
    ME

    View Slide

  34. State
    FOO BAR BAZ
    ME
    Foo

    View Slide

  35. State
    FOO BAR BAZ
    ME
    Foo
    Bar

    View Slide

  36. State
    FOO BAR BAZ
    ME
    Foo Baz
    Bar

    View Slide

  37. Methods
    FOO
    STATE
    PRIVATE
    METHOD
    PRIVATE
    METHOD

    View Slide

  38. I combat complexity
    with Functional
    Programming

    View Slide

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

    View Slide

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

    View Slide

  41. Pure Functions
    (+2)
    3 5
    Always return the same result given
    the same arguments

    View Slide

  42. Pure Functions
    Have no side effects
    FS
    DB
    +

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  48. Immutability
    USER
    WALLABY
    .feed
    .feed

    View Slide

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

    View Slide

  50. Isolate side-effects
    FILE
    DB
    HTTP

    View Slide

  51. Isolate side-effects
    FILE
    DB
    HTTP

    View Slide

  52. Isolate side-effects
    FILE
    DB
    HTTP

    View Slide

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

    View Slide

  54. composing side-effects
    web_request

    View Slide

  55. composing side-effects
    web_request
    |> transform_request_into_query

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  62. Example

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  66. Separate arrangement & work

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  75. COULD I MAKE THIS
    AN API CALL?

    View Slide

  76. COULD I MAKE THIS
    CONCURRENT?

    View Slide

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

    View Slide

  78. Conclusion

    View Slide

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

    View Slide

  80. Lets stop making problems for
    ourselves

    View Slide

  81. Think about the goals of best
    practices.

    View Slide

  82. Build simpler systems

    View Slide

  83. Chris Keathley / @ChrisKeathley / [email protected]
    Thanks!

    View Slide