living-without-objects



living-without-objects

0 0


living-without-objects

Slides of my talk on functional programming: design, composition and polymorphism.Uses reveal.js

On Github ogrigas / living-without-objects

no Objects, no Classes, no Monads

Thinking in functions

 

Osvaldas Grigas | @ogrigas

OOP Hangover

Design

Design

Before After

OO Design

  • Noun-oriented
  • Verb-oriented
  • Domain-driven?

Nouns

  • CustomerDAO
  • CustomerService
  • CustomerController

Verbs

  • RegisterCustomer
  • PromoteCustomerToVIP
  • RenderCustomerProfilePage

OO Design Principles

  • Single Responsibility
  • Interface Segregation
  • Dependency Inversion

Single Responsibility

public class ZipDownloadService {

    public List<File> downloadAndExtract(String location) { }

}

Single Responsibility

public class FileDownloader {

    public List<File> downloadFiles(String location) { ... }

}
public class ZipExtractor {

    public File extractZip(File archive) { ... }

}

Or ... just functions

(defn download-files [location] (...))

(defn extract-zip [archive] (...))

Clojure

in a nutshell

( some-function ( arg1 , arg2 , arg3 )

Interface Segregation

public class ProductCatalog
{
    public ProductId Save(Product product)
    {
        ...
    }

    public Product FindById(ProductId id)
    {
        ...
    }
}

Interface Segregation

public class ProductSaver
{
    public ProductId Save(Product product)
    { ... }
}
public class ProductFinder
{
    public Product FindById(ProductId id)
    { ... }
}

Somethin' ain't right

Interface Segregation

public class ProductRepository
{
    public ProductId Save(Product product)
    { ... }
}
public class ProductQuery
{
    public Product FindById(ProductId id)
    { ... }
}

Feelin' good now

Or ... just functions

(defn save-product [product] (...))

(defn find-product-by-id [id] (...))

Applying OO design principles often leads to...

functional design

What's missing

  • Code organization
  • Encapsulation
  • Inheritance hierarchies
  • Polymorphism

No code organization?

(ns my.product.repository)

(defn save [product] (...))

(defn find-by-id [id] (...))
(require '[my.product.repository :as product-repo])

(product-repo/find-by-id 42)

No encapsulation?

  • Data is not an object
  • Data is immutable

No inheritance hierarchies?

Who cares!

No polymorphism?

We'll get there

Composition

Composition

Avoiding hard-coded dependencies

OO Dependencies

public class ProfilePage {

    public String render(Repository repository, int customerId) {
        return toHtml(repository.loadProfile(customerId));
    }
}
Repository repository = new Repository();
ProfilePage page = new ProfilePage();
String html = page.render(repository, customerId);

FP Dependencies

(defn render-page [repository-fn customer-id]
  (to-html (repository-fn customer-id)))
(defn load-profile [customer-id]
  (...))
(render-page load-profile customer-id)

OO Composition

ProfilePage pageInjected = new ProfilePage(new Repository());
pageInjected.render(customerId);

Function closure

(def render-injected
  (fn [customer-id] (render-page load-profile customer-id)))
(render-injected customer-id)

Partial application

(def render-injected (partial render-page load-profile))
(render-injected customer-id)

"Adapter" pattern

(defn to-view-model [profile] (...))

(render-page (comp to-view-model load-profile) customer-id)

"Decorator" pattern

(defn with-logging [f]
  (fn [& args]
    (log/debug "Called with params" args)
    (def [result (apply f args)]
      (log/debug "Returned" result)
      result)))

(render-page (with-logging load-profile) customer-id)

Polymorphism

Polymorphism

I don't know.I don't want to know.

Basic FP polymorphism

  • All functions implement the "Strategy" pattern

OO Polymorphism

  • Interfaces / Subclasses
  • Dispatch on the type of first argument
public interface JsonObj {
    String toJson();
}
public class JsonString implements JsonObj {
    private final String value;

    public JsonString(String value) {
        this.value = value;
    }

    public String toJson() {
        return "\"" + value + "\"";
    }
}
public class JsonList implements JsonObj {
    private final List<? extends JsonObj> list;

    public JsonString(List<? extends JsonObj> list) {
        this.list = list;
    }

    public String toJson() {
        return "[" +
            list.stream()
                .map(JsonObj::toJson)
                .collect(joining(",")) +
            "]";
    }
}
JsonObj obj = new JsonList(asList(
      new JsonString("a"),
    new JsonList(asList(
        new JsonString("b"),
        new JsonString("c")
    )),
    new JsonString("d")
));

System.out.println(obj.toJson());

// ["a",["b","c"],"d"]

Limitations

  • Need wrapper types
  • Cannot extend existing types

Too constraining!

Clojure Protocols

dispatch on the type of first argument

(defprotocol Json
  (to-json [this]))
(extend-type String Json
  (to-json [this]
    (str "\"" this "\"")))
(extend-type List Json
  (to-json [this]
    (str "[" (->> this (map to-json) (string/join ",")) "]")))
(extend-type nil Json
  (to-json [this]
    "null"))
(to-json ["a" ["b" "c"] nil "d"])

;;=> ["a",["b","c"],null,"d"]

Why stop there?

Clojure Multimethods

dispatch on anything!

(defmulti greet :country)

(defmethod greet "PL" [person]
  (println "Dzień dobry," (:name person)))

(defmethod greet "FR" [person]
  (println "Bonjour," (:name person) "!"))

(defmethod greet :default [person]
  (println "Hello," (:name person)))
(greet {:name "Jacques" :country "FR"})

;;=> Bonjour, Jacques !

Conclusion

  • Design
  • Composition
  • Polymorphism

OOP ⇄ FP

(questions? "Osvaldas Grigas" @ogrigas)
no Objects, no Classes, no Monads Thinking in functions   Osvaldas Grigas | @ogrigas