Skip to content

Instantly share code, notes, and snippets.

@jasongilman
Last active January 16, 2017 22:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jasongilman/8c48af6be6111a4ab64b to your computer and use it in GitHub Desktop.
Save jasongilman/8c48af6be6111a4ab64b to your computer and use it in GitHub Desktop.
Transparent Functions
;; Transparent Functions
;; I was experimenting with transducers and wanted a way to understand how they worked. Transducer
;; code uses many nested functions in various locations with other nested functions defined as local
;; variables in scope. Typically after an anonymous Clojure function is defined you have no visibility
;; into the locals that were in scope when the function was defined, where the function came from,
;; or the code in the function. I defined a macro, tfn, that creates a transparent function. It's
;; a normal Clojure function with additional metadata including the function code and local
;; variable names and values.
(require 'clojure.pprint
'clojure.string)
(defmacro tfn
"Creates a function with additional metadata including the local variables names and values that
are in scope and the function code."
[& parts]
(let [ks (keys &env)]
`(vary-meta
(fn ~@parts)
assoc
:tfn? true
:locals (zipmap '~ks [~@ks])
:code '~&form)))
(defn tfn?
"Returns true if the function is a transparent function."
[f]
(true? (:tfn? (meta f))))
(def ^:private print-order-map
"Mapping of metadata keys to the order they should be displayed."
{:locals 0
:code 1})
(def ^:private sorted-map-by-print-order
"Helper sorted map whose keys are sorted in the order to be printed."
(sorted-map-by #(compare (print-order-map %1) (print-order-map %2))))
(defn- displayable-value
"Converts a value for display."
[v]
(let [types (ancestors (type v))]
(cond
(tfn? v)
(let [{:keys [locals code]} (meta v)
print-locals (into {} (for [[k v] locals] [k (displayable-value v)]))]
(assoc sorted-map-by-print-order :locals print-locals :code code))
(types clojure.lang.IFn)
(-> v
type
.getName
(clojure.string/replace "$" "/")
(clojure.string/replace "clojure.core/" "")
symbol)
:else
v)))
;; Implementation of pretty print simple dispatch for displaying transparent functions.
(defmethod clojure.pprint/simple-dispatch clojure.lang.AFunction [v]
(if (tfn? v)
(clojure.pprint/simple-dispatch (displayable-value v))
(pr v)))
;; Example Usage of transparent functions
;; Define a function that returns a function.
(defn make-increment-by
[n]
(tfn [v] (+ v n)))
;; Function in use
(let [seven-up (make-increment-by 7)]
(seven-up 14))
;; => 21
;; Examining a transparent function can be done by getting the metadata
(meta (make-increment-by 7))
;; => {:code (tfn [v] (+ v n)), :locals {n 7}, :tfn? true}
;; Or through pprint (preferred way)
(clojure.pprint/pprint (make-increment-by 7))
;; Prints
;; {:locals {n 7}, :code (tfn [v] (+ v n))}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Transparent Functions applied to Transducers
;; I used this to help understand the new transducers code.
;; Example map function (just transducer arity) taken from Clojure modified to use tfn macro
(defn map-tfn
[f]
(tfn [f1]
(tfn
([] (f1))
([result] (f1 result))
([result input]
(f1 result (f input)))
([result input & inputs]
(f1 result (apply f input inputs))))))
;; What does a transducer look like?
(clojure.pprint/pprint (map-tfn inc))
;; Printed
; {:locals {f inc},
; :code
; (tfn
; [f1]
; (tfn
; ([] (f1))
; ([result] (f1 result))
; ([result input] (f1 result (f input)))
; ([result input & inputs] (f1 result (apply f input inputs)))))}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; What do composed transducers look like?
;; Copy of comp with transparent functions.
(defn comp-tfn
([] identity)
([f] f)
([f g]
(tfn
([] (f (g)))
([x] (f (g x)))
([x y] (f (g x y)))
([x y z] (f (g x y z)))
([x y z & args] (f (apply g x y z args)))))
([f g & fs]
(reduce comp-tfn (list* f g fs))))
(clojure.pprint/pprint (comp-tfn (map-tfn inc) (map-tfn str)))
;; This isn't very readable. The next step will improve things.
;; Printed
; {:locals
; {f
; {:locals {f inc},
; :code
; (tfn
; [f1]
; (tfn
; ([] (f1))
; ([result] (f1 result))
; ([result input] (f1 result (f input)))
; ([result input & inputs] (f1 result (apply f input inputs)))))},
; g
; {:locals {f str},
; :code
; (tfn
; [f1]
; (tfn
; ([] (f1))
; ([result] (f1 result))
; ([result input] (f1 result (f input)))
; ([result input & inputs] (f1 result (apply f input inputs)))))}},
; :code
; (tfn
; ([] (f (g)))
; ([x] (f (g x)))
; ([x y] (f (g x y)))
; ([x y z] (f (g x y z)))
; ([x y z & args] (f (apply g x y z args))))}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; What does a composed transducer look like after the step function has been applied?
;; Here I'm passing in conj to the transducer as would happen if clojure.core/into were
;; called with a transducer
(clojure.pprint/pprint ((comp-tfn (map-tfn inc) (map-tfn str)) conj))
;; Printed
; {:locals
; {f inc,
; f1
; {:locals {f str, f1 conj},
; :code
; (tfn
; ([] (f1))
; ([result] (f1 result))
; ([result input] (f1 result (f input)))
; ([result input & inputs] (f1 result (apply f input inputs))))}},
; :code
; (tfn
; ([] (f1))
; ([result] (f1 result))
; ([result input] (f1 result (f input)))
; ([result input & inputs] (f1 result (apply f input inputs))))}
;; The print out shows how transducers are connected to one another. The step function for a
;; transducer in a chain is the previous transducer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment