Skip to content

Instantly share code, notes, and snippets.

@oxalorg
Last active April 20, 2022 15:42
Show Gist options
  • Save oxalorg/8b8f7026a7c79cbd270b518bbfc8a14e to your computer and use it in GitHub Desktop.
Save oxalorg/8b8f7026a7c79cbd270b518bbfc8a14e to your computer and use it in GitHub Desktop.
(ns poly
(:require
[clojure.string :as str]))
;; =====================================================
;; what is a good way to do polymorphism on an example like this:
;; =====================================================
(defn transform-messages [messages]
(map #(update % :text str/upper-case) messages))
;; transform-messages expects a list of messages
(transform-messages [{:text "hey"}
{:text "hello"}])
;; => ({:text "HEY"} {:text "HELLO"})
;; but over the span of months we see this pattern emerge everywhere in the codebase
(first
(transform-messages [{:text "hey"}]))
;; => {:text "HEY"}
;; =====================================================
;; simplest approach would be to implement a different function, but that makes
;; it even more confusing (maybe? maybe-not?).
;; Can we hope to have runtime polymorphism to make a convenient API for the
;; caller?
;; =====================================================
;; Option 1
;; =====================================================
(defn transform-messages2 [messages]
(if-not (sequential? messages)
(first
(transform-messages [messages]))
(map #(update % :text str/upper-case) messages)))
(transform-messages2 [{:text "hey"}
{:text "hello"}])
;; => ({:text "HEY"} {:text "HELLO"})
(transform-messages2 {:text "hey"})
;; => {:text "HEY"}
;; =====================================================
;; Option #2
;; =====================================================
(defprotocol Transformer
(transform-messages3 [messages]))
(extend-protocol Transformer
clojure.lang.Sequential
(transform-messages3 [messages]
(map #(update % :text str/upper-case) messages))
clojure.lang.IPersistentMap
(transform-messages3 [message]
(first (transform-messages [message]))))
(transform-messages3 [{:text "hey"}])
;; => ({:text "HEY"})
(transform-messages3 {:text "hey"})
;; => {:text "HEY"}
;; =====================================================
;; Option #3
;; =====================================================
(defmulti transform-messages4 (fn [messages]
(type messages)))
(defmethod transform-messages4 clojure.lang.Sequential
[messages]
(map #(update % :text str/upper-case) messages))
(defmethod transform-messages4 clojure.lang.IPersistentMap
[message]
(first (transform-messages4 [message])))
(transform-messages4 [{:text "hey"}])
;; => ({:text "HEY"})
(transform-messages4 {:text "hey"})
;; => {:text "HEY"}
;; =====================================================
;; Option #4
;; =====================================================
;; something I haven't thought of?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment