Last active
January 16, 2017 22:40
-
-
Save jasongilman/8c48af6be6111a4ab64b to your computer and use it in GitHub Desktop.
Transparent Functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; 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