life-without-objects



life-without-objects

0 0


life-without-objects

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

On Github ogrigas / life-without-objects

 

 Life w/o Objects

 OOP → Functional  Design 

 with Clojure

 

 

(Osvaldas Grigas | @ogrigas)

OOP Hangover

"hybrid" languages

type systems

Design

Monolithic Design

Modular Design

Design process

Before After

OO Design

  • Noun-oriented
  • Verb-oriented

Nouns

  • CustomerDAO
  • CustomerService
  • CustomerController

Verbs

  • RegisterCustomer
  • PromoteCustomerToVIP
  • RenderCustomerProfilePage

Domain-driven design?

OO Design Principles

  • Single Responsibility
  • Interface Segregation
  • Dependency Inversion

Single Responsibility

public class ZipDownloadService {

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

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

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

Or ... just functions

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

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

Clojure

in a nutshell


Clojure

in a nutshell

 calculate(5, 2, 9)

Clojure

in a nutshell

(calculate 5, 2, 9)

Clojure

in a nutshell

(calculate 5 2 9)

Function definition

(defn calculate [a b c]
  (+ a (* b c)))

Interface Segregation

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

    public Product FindById(int productId)
    {
        ...
    }
}
public class ProductSaver
{
    public void Save(Product product)
    {
        ...
    }
}
public class ProductFinder
{
    public Product FindById(int id)
    {
        ...
    }
}

Somethin' ain't right

public class ProductRepository
{
    public void Save(Product product)
    {
        ...
    }
}
public class ProductQuery
{
    public Product FindById(int 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

What's missing

  • Code organization
  • Encapsulation
  • Inheritance hierarchies
  • Composition & Polymorphism

Code organization

(ns my.product.repository)

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

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

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

Encapsulation

Encapsulation

  • Data is not an object
  •  

Encapsulation

  • Data is not an object
  • Data is immutable
(html [:body
        [:h1 "Employees"]
        [:ul
        (for [person employees]
          [:li
            [:h2 (:name person)]
            [:p (:email person)]])]])
(defn handle [request]
  {:status 200
   :headers {"Content-Type" "application/json"
             "X-Custom-Header" "12345"}
   :body (json/generate-string
           {:name "John Wick"
            :age 42
            :email "john@wick.name"
            :hobbies ["guns" "dogs" "judo"]})})

append-child array-map assoc associative? assoc-in bean bigdec bigint biginteger bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor blank? branch? butlast byte capitalize cat char char? char-escape-string char-name-string children coll? compare concat conj cons contains? count counted? cycle dec dec' decimal? dedupe dense-int-set diff difference disj dissoc distinct distinct? double down drop drop-last drop-while edit eduction empty empty? end? enumeration-seq escape even? every? false? ffirst file-seq filter filterv find find-keyword first flatten float float? fnext for format frequencies gensym get get-in group-by hash-map hash-set identical? inc inc' index insert-child insert-left insert-right int integer? interleave interpose intersection int-map into into-array int-set iterate iterator-seq join keep keep-indexed key keys keyword keyword? last lazy-cat lazy-seq left leftmost lefts line-seq list list? list* long lower-case make-node map map? mapcat map-indexed map-invert mapv max max-key merge merge-with min min-key mod neg? next nfirst nil? nnext node not not= not-any? not-empty not-every? nth nthnext nthrest num number? odd? partition partition-all partition-by path peek pmap pop pos? postwalk postwalk-demo postwalk-replace prev prewalk prewalk-demo prewalk-replace priority-map project quot rand rand-int rand-nth random-sample range ratio? rational? rationalize reduce reduce-kv reductions re-find re-groups rem re-matcher re-matches remove rename rename-keys re-pattern repeat repeatedly replace replace-first re-quote-replacement re-seq rest resultset-seq reverse reversible? right rightmost rights root rseq rsubseq second select select-keys seq seq? seque sequence sequential? seq-zip set set? short shuffle some some? sort sort-by sorted? sorted-map sorted-map-by sorted-set sorted-set-by split split-at split-lines split-with str string? subs subseq subset? subvec superset? symbol symbol? take take-last take-nth take-while to-array-2d transduce tree-seq trim triml trim-newline trimr true? unchecked-add unchecked-dec unchecked-inc unchecked-multiply unchecked-negate unchecked-subtract union unsigned-bit-shift-right up update update-in upper-case val vals vec vector vector? vector-of vector-zip walk when-first xml-seq xml-zip zero? zipmap

Process / Entity / State Machine

Inheritance hierarchies

 

Inheritance hierarchies

just don't

Composition & Polymorphism

Composition

Composition

Avoiding hard-coded dependencies

OO Dependencies

public class ProfilePage {

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

FP Dependencies

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

Basic FP polymorphism

All functions implement the "Strategy" pattern

OO Composition

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

Function closure

(defn inject [f arg1]
  (fn [arg2] (f arg1 arg2)))
(def render-from-db
  (inject render-page load-from-db))
(render-from-db customer-id)

Partial application

(def render-from-db
  (partial render-page load-from-db))
(render-from-db customer-id)

"Object is a collection of partially-applied functions."

( J.B. Rainsberger )

"Adapter" pattern

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

(render-page (comp to-view-model load-from-db) id)

Polymorphism

OO Polymorphism

"Subtype Polymorphism"

(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<JsonObj> items;

    public JsonString(JsonObj... items) {
        this.items = asList(items);
    }

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

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

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

Limitations

Subtyping is coupled to implementation

 

 

Limitations

Subtyping is coupled to implementation

... cannot extend existing types

 

Limitations

Subtyping is coupled to implementation

... cannot extend existing types

... need wrapper classes

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 "[" (str/join "," (map to-json this)) "]")))
(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!

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 !

the takeaway

(google "Clojure Made Simple")
(questions? "Osvaldas Grigas" @ogrigas)
   Life w/o Objects  OOP → Functional  Design   with Clojure     (Osvaldas Grigas | @ogrigas)