On Github ogrigas / life-without-objects
(Osvaldas Grigas | @ogrigas)
"hybrid" languages
type systems
Domain-driven design?
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] (...))
in a nutshell
in a nutshell
calculate(5, 2, 9)
in a nutshell
(calculate 5, 2, 9)
in a nutshell
(calculate 5 2 9)
(defn calculate [a b c] (+ a (* b c)))
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
(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)
(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
just don't
Avoiding hard-coded 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);
(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)
All functions implement the "Strategy" pattern
ProfilePage page = new ProfilePage(new Repository());
page.render(customerId);
(defn inject [f arg1] (fn [arg2] (f arg1 arg2)))
(def render-from-db (inject render-page load-from-db))
(render-from-db customer-id)
(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 )
(defn to-view-model [profile] (...)) (render-page (comp to-view-model load-from-db) id)
"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"]
Subtyping is coupled to implementation
Subtyping is coupled to implementation
... cannot extend existing types
Subtyping is coupled to implementation
... cannot extend existing types
... need wrapper classes
Too constraining!
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?
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 !
(google "Clojure Made Simple")
(questions? "Osvaldas Grigas" @ogrigas)