Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to specify fixed width floats in specific tagged-literals for output purposes #83

Open
dgtized opened this issue May 21, 2022 · 2 comments

Comments

@dgtized
Copy link

dgtized commented May 21, 2022

I've been working with debugging larger datastructures containing many instances of the shape and vector objects from https://github.com/thi-ng/geom. I've found fipp to be very useful for ensuring the debug output is readable, so thank you.

However, I've also found it useful to simplify the output by doing tricks like:

(extend-protocol IEdn
  thi.ng.geom.vector.Vec2
  (-edn [s]
    (let [[x y] s]
      (tagged-literal 'v2 [(.toFixed x 2) (.toFixed y 2)])))

So that instead of verbose output like

#thi.ng.geom.vector.Vec2[0.15568929809,5.54892085920385]

It's simplified to

#v2[0.16, 5.55]

However, .toFixed is converting the output element type to string too early, so the visitor pass detects it as a string and instead it prints like:

#v2["0.16", "5.55"]

I've succeeded in getting output without the string by rounding each of the x,y coordinate values to two decimal places, keeping them a number, but that occasionally expands back to the max width for each coordinate due to floating point representation errors.

So my question is, is there a way to control formatting by passing through a fixed float, or is this simply behavior that is out of the scope of this library?

Is there a way to pass through formatting instructions during the EDN conversion prior to visiting each element? Or is this attempting to do the transformation in the wrong place, and instead should look into something akin to fipp.clojure to adjust formatting in the output. However based on

fipp/src/fipp/edn.cljc

Lines 55 to 58 in a4cb207

(visit-seq [this x]
(if-let [pretty (symbols (first x))]
(pretty this x)
(pretty-coll this "(" x :line ")" visit)))

It looks like the pretty symbols override only works for '(symbol a b c) forms, but there does not appear to be equivalent functionality for overriding tagged-literals in

fipp/src/fipp/edn.cljc

Lines 71 to 76 in a4cb207

(visit-tagged [this {:keys [tag form]}]
[:group "#" (str tag)
(when (or (and print-meta (meta form))
(not (coll? form)))
" ")
(visit this form)])
Would it make sense to add another map key like :symbols, such as :tagged-literals that supported overriding the printer visitor for each tagged literal? Should I look into writing my own EdnPrinter with this functionality?

Happy to work on a PR if there is a recommended approach or even just to document this behavior, but am uncertain which way is intended. Thanks again for creating such a useful pretty printer interface.

@brandonbloom
Copy link
Owner

I think there are three separate issues wrapped up in here.

  1. control over floating point printing.
  2. extensible formatting of tagged-literals, similar to what is supported symbols.

.toFixed is converting the output element type to string too early, so the visitor pass detects it as a string

There are two ugly workarounds for your immediate needs:

  1. Do something like (symbol (.toFixed x 2)), abusing the fact that Clojure's symbol constructor follows the garbage-in, garbage-out philosophy.
  2. Monkey-patch the print-method for Float and Double:
 (defmethod print-method Float [o, ^Writer w]
  (cond
    (= Float/POSITIVE_INFINITY o) (.write w "##Inf")
    (= Float/NEGATIVE_INFINITY o) (.write w "##-Inf")
    (.isNaN ^Float o) (.write w "##NaN")
    :else (.write w (.toFixed o 2))))

is there a way to control formatting by passing through a fixed float

We delegate to Clojure here and - unfortunately - Clojure's print-method for numbers (modified above) does not provide a parameter for controlling this.

I'd be open to providing an option for this, but I'm hesitant because duplicating an underlying print method for a primitive decouples us from future changes. For instance, when #inf was added, Fipp got support for it for free by delegating to Clojure.

Is there a way to pass through formatting instructions during the EDN conversion prior to visiting each element

No. An intentional design feature of the EDN conversion is that it is formatting-agnostic. In theory, a custom type could be used to pass through formatting hints, but I haven't seen a good reason to do so yet.

Would it make sense to add another map key like :symbols, such as :tagged-literals that supported overriding the printer visitor for each tagged literal?

Maybe? I'd have to think about the implications of that. My gut reaction is to say no unless additional use cases are understood.

Should I look into writing my own EdnPrinter with this functionality?

That's the less hacky workaround: Simply copy/paste this part of the implementation and modify as necessary. It's small enough to be a low maintenance burden.

--

In summary, let me know if one of the workarounds works for you. If number formatting control is important, we can survey/lobby other printers and Clojure itself in case it makes sense to add a clojure.core/number-precision or similar.

@dgtized
Copy link
Author

dgtized commented May 23, 2022

Oh, the symbol approach is a really useful hack to be aware of for pass-through even if it's extra gross. I think that might be enough of a work-around for the cases I'm dealing with for now. Otherwise I'll take a look at my own EdnPrinter implementation with the :tagged-literals override. That seems like the correct spot to override output formatting, but I can understand if it's too niche a use case to update here.

Thanks for pointing out the print-method override approach, but I think I want scoped control over the precision of output and not a global change. It's a useful override if the floating point values are coordinates in a vector, but less useful if they represent something else. That said, I guess it's also possible to override the display of all floating point numbers using a custom EdnVisitor by overriding visit-number to check integer?, float? and format the output there.

Thanks for all the help, I'm trying to think how this could be better documented, but this discussion might be sufficient for now. I'll play around with the symbol and custom EdnVisitor with tagged-literal overrides as those seem to make the most sense and then see if anything to document jumps out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants