Custom pprint for defrecord in nested structure [duplicate] - clojure

This question already has answers here:
pretty-printing a record using a custom method in Clojure
(3 answers)
Closed 4 years ago.
Using Clojure 1.8.0
I'm trying to get a defrecord with custom formatting in an otherwise default-formatted nested structure, for use with EDN, so I need a tagged representation. I could get away with the default one if it would come through in pprint, but I'd prefer a custom one. As it is, I can't get pprint to show the custom one without resorting to setting *print-pprint-dispatch* to pr, which destroys the nice line breaks, etc., that pprint provides.
user> (defrecord junkrecord [val])
user> (def junk1 (->junkrecord 10))
user> (def junk2 (->junkrecord 20))
user> (pprint {:key1 junk1, :key2 junk2, :key3 (java.time.LocalTime/now)})
{:key1 {:val 10},
:key2 {:val 20},
:key3 #object[java.time.LocalTime 0xbf97341 "15:04:43.487"]}
The defrecord shows up without the hashtag like the LocalTime object does, but the hashtag for the defrecord is what I want. This issue is mentioned as unresolved in https://dev.clojure.org/jira/browse/CLJ-1890 .
I created a print-method for my defrecord which works properly when used with (pr ...).
user> (defmethod print-method junkrecord [obj writer] (.write writer (str "#ThisIsWhatIwant" (.val obj))))
user> (pr junk1)
#ThisIsWhatIwant10
However when I run it through pprint, I lose the indentation, line breaks, etc.
user> (with-pprint-dispatch pr
(pprint {:key1 junk1, :key2 junk2, :key3 (java.time.LocalTime/now)}))
{:key1 #ThisIsWhatIwant10, :key2 #ThisIsWhatIwant20, :key3 #object[java.time.LocalTime 0xa908e55 "15:10:09.634"]}
I was able to get it to work for a deftype as it behaves much more like a Java class in this regard, but deftypes are recommended for "low level" stuff, not domain stuff like a defrecord.
user> (deftype junktype [val])
user> (def junk3 (junktype. 30))
user> (pprint {:key1 junk3, :key2 (java.time.LocalTime/now)})
{:key1 #object[user.junktype 0x54c21b73 "user.junktype#54c21b73"],
:key2 #object[java.time.LocalTime 0x20545fc3 "15:17:40.580"]}
user> (defmethod print-method junktype [obj writer] (.write writer (str "#ThisIsWhatIwant" (.val obj))))
user> (pprint {:key1 junk3, :key2 (java.time.LocalTime/now)})
{:key1 #ThisIsWhatIwant30,
:key2 #object[java.time.LocalTime 0x499bdba8 "15:18:33.230"]}
I also played around with *print-dup* and (print-dup ...), etc. but this didn't yield anything.
So how do I get custom tagged printing for a defrecord while using pprint for the nice formatting? I've searched high and low, but have not found anything specific to this problem.
thanks!

The way I see it:
it seems it was proposed but did not make it to clojure 1.8 or 1.9
setup:
user=> (defrecord foo [val])
user.foo
user=> (def x (->foo 33))
#'user/x
now
Either) drop pprint and use str or some such
user=> (str {:a-foo x})
"{:a-foo #user.foo{:val 33}}"
Or) provide a custom dispatch for your type
user=> (defmethod clojure.pprint/simple-dispatch user.foo [f] (pr f))
#object[clojure.lang.MultiFn 0x401f3c4 "clojure.lang.MultiFn#401f3c4"]
user=> (pprint {:a-foo x :a-goo x :b x :c x :d x :f x})
{:a-foo #user.foo{:val 33},
:a-goo #user.foo{:val 33},
:b #user.foo{:val 33},
:c #user.foo{:val 33},
:d #user.foo{:val 33},
:f #user.foo{:val 33}}
for completeness sake - reading:
user=> (clojure.edn/read-string
{:readers {'user.foo map->foo}}
"{:a-foo #user.foo{:val 33}}")
{:a-foo #user.foo{:val 33}}

Related

Passing compile-time state between nested macros in Clojure

I'm trying to write a macro that can be used both in a global and nested way, like so:
;;; global:
(do-stuff 1)
;;; nested, within a "with-context" block:
(with-context {:foo :bar}
(do-stuff 2)
(do-stuff 3))
When used in the nested way, do-stuff should have access to {:foo :bar} set by with-context.
I've been able to implement it like this:
(def ^:dynamic *ctx* nil)
(defmacro with-context [ctx & body]
`(binding [*ctx* ~ctx]
(do ~#body)))
(defmacro do-stuff [v]
`(if *ctx*
(println "within context" *ctx* ":" ~v)
(println "no context:" ~v)))
However, I've been trying to shift the if within do-stuff from runtime to compile-time, because whether do-stuff is being called from within the body of with-context or globally is an information that's already available at compile-time.
Unfortunately, I've not been able to find a solution, because nested macros seem to get expanded in multiple "macro expansion runs", so the dynamic binding of *ctx* (as set within with-context) is not available anymore when do-stuff gets expanded. So this does not work:
(def ^:dynamic *ctx* nil)
(defmacro with-context [ctx & body]
(binding [*ctx* ctx]
`(do ~#body)))
(defmacro do-stuff [v]
(if *ctx*
`(println "within context" ~*ctx* ":" ~v)
`(println "no context:" ~v)))
Any ideas how to accomplish this?
Or is my approach totally insane and there's a pattern for how to pass state in such a way from one macro to a nested one?
EDIT:
The body of with-context should be able to work with arbitrary expressions, not only with do-stuff (or other context aware functions/macros). So something like this should also be possible:
(with-context {:foo :bar}
(do-stuff 2)
(some-arbitrary-function)
(do-stuff 3))
(I'm aware that some-arbitrary-function is about side effects, it might write something to a database for example.)
When the code is being macroexpanded, Clojure computes a fixpoint:
(defn macroexpand
"Repeatedly calls macroexpand-1 on form until it no longer
represents a macro form, then returns it. Note neither
macroexpand-1 nor macroexpand expand macros in subforms."
{:added "1.0"
:static true}
[form]
(let [ex (macroexpand-1 form)]
(if (identical? ex form)
form
(macroexpand ex))))
Any binding you establish during the execution of a macro is no more in place when you exit your macro (this happens inside macroexpand-1). By the time an inner macro is being expanded, the context is long gone.
But, you can call macroexpand directly, in which case the binding are still effective. Note however that in your case, you probably need to call macroexpand-all.
This answer explains the differences between macroexpand and clojure.walk/macroexpand-all: basically, you need to make sure all inner forms are macroexanded.
The source code for macroexpand-all shows how it is implemented.
So, you can implement your macro as follows:
(defmacro with-context [ctx form]
(binding [*ctx* ctx]
(clojure.walk/macroexpand-all form)))
In that case, the dynamic bindings should be visible from inside the inner macros.
I'd keep it simple.
This is solution avoids state in an additional *ctx* variable. I think it is a more functional approach.
(defmacro do-stuff
([arg1 context]
`(do (prn :arg1 ~arg1 :context ~context))
{:a 4 :b 5})
([arg1]
`(prn :arg1 ~arg1 :no-context)))
(->> {:a 3 :b 4}
(do-stuff 1)
(do-stuff 2))
output:
:arg1 1 :context {:a 3, :b 4}
:arg1 2 :context {:b 5, :a 4}
there is one more variant to do this, using some macro magic:
(defmacro with-context [ctx & body]
(let [ctx (eval ctx)]
`(let [~'&ctx ~ctx]
(binding [*ctx* ~ctx]
(do ~#body)))))
in this definition we introduce another let binding for ctx. Clojure's macro system would then put it into the &env variable, accessible by the inner macros at compile-time. Notice that we also keep bindings so that inner functions could use it.
now we need to define the function to get the context value from macro's &env:
(defn env-ctx [env]
(some-> env ('&ctx) .init .eval))
and then you can easily define do-stuff:
(defmacro do-stuff [v]
(if-let [ctx (env-ctx &env)]
`(println "within context" ~ctx ":" ~v)
`(println "no context:" ~v)))
in repl:
user> (defn my-fun []
(println "context in fn is: " *ctx*))
#'user/my-fun
user> (defmacro my-macro []
`(do-stuff 100))
#'user/my-macro
user> (with-context {:a 10 :b 20}
(do-stuff 1)
(my-fun)
(my-macro)
(do-stuff 2))
;;within context {:a 10, :b 20} : 1
;;context in fn is: {:a 10, :b 20}
;;within context {:a 10, :b 20} : 100
;;within context {:a 10, :b 20} : 2
nil
user> (do (do-stuff 1)
(my-fun)
(my-macro)
(do-stuff 2))
;;no context: 1
;;context in fn is: nil
;;no context: 100
;;no context: 2
nil

Accessing argument's metadata in Clojure macro

Is there a way to retrieve the metadata of the arguments inside a clojure macro without using eval? The only thing I could come up with so far is this:
(def ^{:a :b} my-var)
(defmacro my-macro [s] (prn (eval `(meta (var ~s)))))
(my-macro my-var)
;; Prints {:a :b, :name my-var, ...}
I ended up finding a solution:
(def ^{:a :b} my-var)
(defmacro my-macro [s] (prn (meta (resolve s))))
(my-macro my-var)
;; Prints {:a :b, :name my-var, ...}
So the key part here is to use resolve function to get the var associated to the symbol.

DSL syntax with optional parameters

I'm trying to handle following DSL:
(simple-query
(is :category "car/audi/80")
(is :price 15000))
that went quite smooth, so I added one more thing - options passed to the query:
(simple-query {:page 1 :limit 100}
(is :category "car/audi/80")
(is :price 15000))
and now I have a problem how to handle this case in most civilized way. as you can see simple-query may get hash-map as a first element (followed by long list of criteria) or may have no hash-mapped options at all. moreover, I would like to have defaults as a default set of options in case when some (or all) of them are not provided explicite in query.
this is what I figured out:
(def ^{:dynamic true} *defaults* {:page 1
:limit 50})
(defn simple-query [& body]
(let [opts (first body)
[params criteria] (if (map? opts)
[(merge *defaults* opts) (rest body)]
[*defaults* body])]
(execute-query params criteria)))
I feel it's kind of messy. any idea how to simplify this construction?
To solve this problem in my own code, I have a handy function I'd like you to meet... take-when.
user> (defn take-when [pred [x & more :as fail]]
(if (pred x) [x more] [nil fail]))
#'user/take-when
user> (take-when map? [{:foo :bar} 1 2 3])
[{:foo :bar} (1 2 3)]
user> (take-when map? [1 2 3])
[nil [1 2 3]]
So we can use this to implement a parser for your optional map first argument...
user> (defn maybe-first-map [& args]
(let [defaults {:foo :bar}
[maybe-map args] (take-when map? args)
options (merge defaults maybe-map)]
... ;; do work
))
So as far as I'm concerned, your proposed solution is more or less spot on, I would just clean it up by factoring out parser for grabbing the options map (here into my take-when helper) and by factoring out the merging of defaults into its own binding statement.
As a general matter, using a dynamic var for storing configurations is an antipattern due to potential missbehavior when evaluated lazily.
What about something like this?
(defn simple-query
[& body]
(if (map? (first body))
(execute-query (merge *defaults* (first body)) (rest body))
(execute-query *defaults* body)))

How can I format a map over several lines with pprint?

pprint's docs are kind of a brick wall. If you pprint a map, it comes out on one line like so: {:a "b", :b "c", :d "e"}. Instead, I'd like to be pprinted like this, optionally with commas:
{:a "b"
:b "c"
:d "e"}
How would one do that with pprint?
You can set the *print-right-margin* binding:
Clojure=> (binding [*print-right-margin* 7] (pprint {:a 1 :b 2 :c 3}))
{:a 1,
:b 2,
:c 3}
Not exactly what you're looking for, but it might be enough?
BTW, the best way to figure this out —or at least the approach I took— is to use
Clojure=> (use 'clojure.contrib.repl-utils)
Clojure=> (source pprint)
(defn pprint
"Pretty print object to the optional output writer. If the writer is not provided,
print the object to the currently bound value of *out*."
([object] (pprint object *out*))
([object writer]
(with-pretty-writer writer
(binding [*print-pretty* true]
(write-out object))
(if (not (= 0 (.getColumn #^PrettyWriter *out*)))
(.write *out* (int \newline))))))
nil
Hmmrmmm.. what does with-pretty-writer do to *out*?
Clojure=> (source clojure.contrib.pprint/with-pretty-writer)
(defmacro #^{:private true} with-pretty-writer [base-writer & body]
`(let [new-writer# (not (pretty-writer? ~base-writer))]
(binding [*out* (if new-writer#
(make-pretty-writer ~base-writer *print-right-margin* *print-miser-width*)
~base-writer)]
~#body
(if new-writer# (.flush *out*)))))
nil
Okay, so *print-right-margin* sounds promising...
Clojure=> (source clojure.contrib.pprint/make-pretty-writer)
(defn- make-pretty-writer
"Wrap base-writer in a PrettyWriter with the specified right-margin and miser-width"
[base-writer right-margin miser-width]
(PrettyWriter. base-writer right-margin miser-width))
nil
Also, this is pretty informative:
Clojure=> (doc *print-right-margin*)
-------------------------
clojure.contrib.pprint/*print-right-margin*
nil
Pretty printing will try to avoid anything going beyond this column.
Set it to nil to have pprint let the line be arbitrarily long. This will ignore all
non-mandatory newlines.
nil
Anyway —and perhaps you already knew even this— if you really want to customize the way that pprint works, you can proxy clojure.contrib.pprint.PrettyWriter and pass that down by binding it to *out*. The PrettyWriter class is pretty large and intimidating, so I'm not sure if this was what you originally meant by your "brick wall" comment.
I don't think you can do that, you'll probably need to write your own, something like:
(defn pprint-map [m]
(print "{")
(doall (for [[k v] m] (println k v)))
(print "}"))

Clojure: How to pass two sets of unbounded parameters?

Contrived example to illustrate:
(def nest1 {:a {:b {:c "foo"}}})
(def nest2 {:d {:e "bar"}})
If I wanted to conj these nests at arbitrary levels, I could explicitly do this:
(conj (-> nest1 :a :b) (-> nest2 :d)) ; yields {:e "bar", :c "foo"}
(conj (-> nest1 :a) (-> nest2 :d)) ; yields {:e "bar", :b {:c "foo"}}
But what if I wanted to create a function that would accept the "depth" of nest1 and nest2 as parameters?
; Does not work, but shows what I am trying to do
(defn join-nests-by-paths [nest1-path nest2-path]
(conj (-> nest1 nest1-path) (-> nest2 nest2-path))
And I might try to call it like this:
; Does not work
(join-nests-by-paths '(:a :b) '(:d))
This doesn't work. I can't simply pass each "path" as a list to the function (or maybe I can, but need to work with it differently in the function).
Any thoughts? TIA...
Sean
Use get-in:
(defn join-by-paths [path1 path2]
(conj (get-in nest1 path1) (get-in nest2 path2)))
user> (join-by-paths [:a :b] [:d])
{:e "bar", :c "foo"}
user> (join-by-paths [:a] [:d])
{:e "bar", :b {:c "foo"}}
Your version is actually doing something like this:
user> (macroexpand '(-> nest1 (:a :b)))
(:a nest1 :b)
which doesn't work, as you said.
get-in has friends assoc-in and update-in, all for working with nested maps of maps. There's a dissoc-in somewhere in clojure.conrtrib.
(In Clojure it's more idiomatic to use vectors instead of quoted lists when you're passing around sequential groups of things.)