Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Life without Objects (w/ Clojure)

1,107 views

Published on

Object-oriented to functional design can be quite challenging, not least because OO programmers are not used to thinking in terms of verbs instead of nouns.

Non-hybrid functional languages do not allow falling back to class hierarchies, so one is forced to learn to think in terms of verbs and find other ways of doing polymorphism and composition. We'll investigate some examples of that in Clojure.

Published in: Technology
  • Be the first to comment

Life without Objects (w/ Clojure)

  1. 1.   ( life w/o objects ) ( oop → functional  design ) ( with clojure )     (Osvaldas Grigas | )@ogrigas
  2. 2. (oop hangover)
  3. 3. type systems
  4. 4. "hybrid" languages
  5. 5. (design)
  6. 6. (monolithic design)
  7. 7. (modular design)
  8. 8. (design process) Before A er
  9. 9. (oo design) Noun-oriented Verb-oriented
  10. 10. (nouns) CustomerDAO CustomerService CustomerController
  11. 11. (verbs) RegisterCustomer PromoteCustomerToVIP RenderCustomerProfilePage
  12. 12. Domain-driven Design?   "As a teller, I want to transfer funds between accounts."
  13. 13. (oo design principles) Single Responsibility Interface Segregation Dependency Inversion
  14. 14. (single responsibility)
  15. 15. public class ZipDownloadService {      List<File> downloadAndExtract(String location) {          ...      } }
  16. 16. public class FileDownloader {      List<File> downloadFiles(String location) {          ...      } } public class ZipExtractor {      File extractZip(File archive) {          ...      } }
  17. 17. Or ... just functions (defn download­files [location]    (...))  (defn extract­zip [archive]    (...))
  18. 18. (clojure) in a nutshell
  19. 19. (clojure) in a nutshell  calculate(5,2,9)
  20. 20. (clojure) in a nutshell (calculate 5,2,9)
  21. 21. (clojure) in a nutshell (calculate 5 2 9)
  22. 22. (function definition) (defn calculate [a b c]    (+ a (* b c)))
  23. 23. (interface segregation)
  24. 24. public class ProductCatalog  {      public void Save(Product product)      {         ...      }     public Product FindById(int productId)      {         ...      } }
  25. 25. public class ProductSaver  {      public void Save(Product product)      {         ...      } } public class ProductFinder  {      public Product FindById(int id)      {         ...      } }
  26. 26. Somethin' ain't right
  27. 27. public class ProductRepository  {      public void Save(Product product)      {         ...      } } public class ProductQuery  {      public Product FindById(int id)      {         ...      } }
  28. 28. Feelin' good now
  29. 29. Or ... just functions (defn save­product [product]    (...))  (defn find­product­by­id [id]    (...))
  30. 30. Applying OO design principles o en leads to functional design
  31. 31. (what's missing)
  32. 32. (what's missing) Code organization Inheritance Encapsulation Composition Polymorphism State management
  33. 33. (code organization)
  34. 34. (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)
  35. 35. (inheritance)  
  36. 36. (inheritance) just don't
  37. 37. (encapsulation)
  38. 38. (encapsulation) Data is not an object  
  39. 39. (encapsulation) Data is not an object Data is immutable
  40. 40. (html [:body          [:h1 "Employees"]          [:ul          (for [person employees]            [:li              [:h2 (:name person)]              [:p (:email person)]])]])
  41. 41. (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"]})})
  42. 42. 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-shi -le bit-shi -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-le 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 le le most le s 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-shi -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
  43. 43. (composition)
  44. 44. (dependency inversion) Good for decoupling Good for isolated testing
  45. 45. (oo dependencies) public class ProfilePage {      String render(Repository repo, int customerId) {          return toHtml(repo.loadProfile(customerId));      } } Repository repo = new SqlRepository();  ProfilePage page = new ProfilePage(); String html = page.render(repo, customerId);
  46. 46. (fp dependencies) (defn render­page [repository­fn customer­id]    (to­html (repository­fn customer­id))) (defn load­from­sql [customer­id]    (...)) (render­page load­from­sql customer­id)
  47. 47. (basic fp polymorphism) All functions implement the "Strategy" pattern
  48. 48. (oo composition) Repository repo = new SqlRepository();  ProfilePage page = new ProfilePage(repo); page.render(customerId);
  49. 49. (function closure) (defn inject [f arg1]    (fn [arg2] (f arg1 arg2))) (def render­from­db    (inject render­page load­from­sql)) (render­from­db customer­id)
  50. 50. (partial application) (def render­from­db    (partial render­page load­from­sql)) (render­from­db customer­id)
  51. 51. "Object is a collection of partially-applied functions." ( J.B. Rainsberger )
  52. 52. (structural patterns) Adapter Decorator ...
  53. 53. ("adapter" pattern) (defn to­view­model [profile] (...))  (render­page (comp to­view­model load­from­sql) id)
  54. 54. ("decorator" pattern) (defn traceable [f]    (fn [& args]      (log/trace "Called with params:" args)      (apply f args))) (render­page (traceable load­from­sql) id)
  55. 55. (polymorphism)
  56. 56. (oo polymorphism) "Subtype Polymorphism" (dispatch on the type of first argument)
  57. 57. public interface JsonObj {      String toJson();  }
  58. 58. public class JsonString implements JsonObj {      private final String value;      public JsonString(String value) {          this.value = value;      }     public String toJson() {          return """ + value + """;      } }
  59. 59. 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(",")) + "]";      } }
  60. 60. 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"]
  61. 61. (limitations) Subtyping is coupled to implementation    
  62. 62. (limitations) Subtyping is coupled to implementation ... cannot extend existing types  
  63. 63. (limitations) Subtyping is coupled to implementation ... cannot extend existing types ... need wrapper classes
  64. 64. Too constraining!
  65. 65. (clojure protocols) dispatch on the type of first argument
  66. 66. (defprotocol Json    (to­json [this]))
  67. 67. (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"))
  68. 68. (to­json ["a" ["b" "c"] nil "d"])  ;;=> ["a",["b","c"],null,"d"]
  69. 69. Why stop there?
  70. 70. (clojure multimethods) dispatch on anything!
  71. 71. (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 !
  72. 72. (state management)
  73. 73. State Machine / Business Process / Entity
  74. 74. "Class" as a namespace (def Account    {'deposit  (fn [state amount]                 (update state :balance + amount))     'withdraw (fn [state amount]                 (if (< amount (state :balance))                   (update state :balance ­ amount)                   state))}) (def state0 {:balance 0}) (def state1 ((Account 'deposit)  state0 100))  (def state2 ((Account 'withdraw) state1 30))  (def state3 ((Account 'withdraw) state2 20))
  75. 75. Partially apply state with "constructor" (defn new­object [clazz initial­state]    ..........      ..........        ..........) (def acc (new­object Account {:balance 0}))  (acc 'deposit 100)  (acc 'withdraw 30)  (acc 'withdraw 20)
  76. 76. Partially apply state with "constructor" (defn new­object [clazz initial­state]    (let [state­ref (atom initial­state)]      ..........        ..........)) (def acc (new­object Account {:balance 0}))  (acc 'deposit 100)  (acc 'withdraw 30)  (acc 'withdraw 20)
  77. 77. Partially apply state with "constructor" (defn new­object [clazz initial­state]    (let [state­ref (atom initial­state)]      (fn [method & args]        ..........))) (def acc (new­object Account {:balance 0}))  (acc 'deposit 100)  (acc 'withdraw 30)  (acc 'withdraw 20)
  78. 78. Partially apply state with "constructor" (defn new­object [clazz initial­state]    (let [state­ref (atom initial­state)]      (fn [method & args]        (apply swap! state­ref (clazz method) args)))) (def acc (new­object Account {:balance 0}))  (acc 'deposit 100)  (acc 'withdraw 30)  (acc 'withdraw 20)
  79. 79. (the takeaway) Eierlegende Wollmilchsau
  80. 80. (the takeaway) Object
  81. 81. (google "Clojure Made Simple") (questions? "Osvaldas Grigas" @ogrigas)

×