expound 2018-10-18

@borkdude has joined the channel

@borkdude I haven’t had a use case for a transform function before, so I’m a hesitant to add a callback w/o understanding the use case more

I’m curious about why transforming the value is better than changing the printer

the use case is printing only a part of the data, not all

because I’m happy with how things are printed, it’s just too much

the issue is the map is too large, even when most of it is omitted?

(i.e. printed with ... placeholders)

does it do that now?

I’ll test

(the example with city/state)

it’s entirely possible that the specific spec+data is not working w/ this feature, which would be good to know

I still get a couple of tens of lines from my map that isn’t that relevant

(being the system map from component)

ah, this is failing on a keys spec, is that correct?

gotcha, yeah, I’ll file an issue but it’s possible to print the summary map in that case

I’ll think about it more

I’m not sure a transform function would be a general solution though - what’s the strategy for picking which part of the map to remove?

It seems like it’d be hard to write a general transformer that doesn’t leave out necessary data in some cases

the strategy is up to the user in the same way the the string function is up to the user

but if you could explain me how to do the summary thing, that might work for me

Sorry, I was unclear - what’s the strategy in your case? If a map is a component map, which keys would you remove?

in my case the strategy would be to just print the keys

Oh sorry, I misspoke: ” it’s possible to print the summary map in that case” - I meant it’s possible expound could do this

If you want to print the keys, would the custom printer work? I guess I’m wondering how you’d use the transform function in this case

(as I do now in the custom printing function, but I lose expound’s nice ability to highlight stuff etc)

I would do :transform keys

(if (map? v) (keys v) v)

Ah, I think that’s going to get confusing though - now the data that is shown to fail is not the same thing

as what you actually entered

so, for instance, the highlighter is going to look really weird

but maybe that’s OK in your use case 🙂

ah, I understand. so falling back on the default printing function is the easier way out then

if it were easily accessible 🙂

In any case, I think you can try this out today, although with some work, because you can modify the explain-data if you wanted

my custom string function now looks like this:

(defn my-value-str [_spec-name form path value]
  (if (and (map? value)
           (every? (fn [ns]
                     (when ns
                       (str/starts-with? ns "")))
                   (map namespace (keys value))))
    (str (keys value))
    (str value)))

e.g.

(set! s/*explain-out* 
  (fn [explain-data] 
    (let [new-explain-data (replace-map-with-keys explain-data)]
      ((expound/custom-printer {,,,}) new-explain-data)))

(untested, and granted it’s not very pretty)

interesting

you should be able to do something like this (untested)

(def default-value-str #'expound/value-in-context)

(defn my-value-str [_spec-name form path value]
  (if (and (map? value)
           (every? (fn [ns]
                     (when ns
                       (str/starts-with? ns "")))
                   (map namespace (keys value))))
    (str (keys value))
    (default-value-str my-printer-opts spec-name form path value)))

untested, and also not super pretty 😞

k, I’ll play a little with this, thanks 🙂

sounds good, I think the second one is probably going to be less work overall, but I could be mistaken

in any case, I agree with you that this is painful, I’ll think more about how to best allow users to control this while allowing them to fallback in a sensible way

This worked:

(declare my-expound-value-str)

(def expound-opts
  {:show-valid-values? false
   :value-str-fn #'my-expound-value-str
   :print-specs? true})

(defn my-expound-value-str
  [_spec-name form path value]
  (if (and (map? value)
           (every? (fn [ns]
                     (when ns
                       (str/starts-with? ns "")))
                   (map (fn [k]
                          (and (keyword? k)
                               (namespace k)))
                        (keys value))))
    (str (keys value))
    (#'expound/value-in-context expound-opts _spec-name form path value)))

(defn my-explain-out
  [explain-data]
  ((expound/custom-printer
    expound-opts) explain-data))

(defn activate-specs
  "Check spec asserts and fdefs"
  []
  (alter-var-root #'s/*explain-out*
                  (constantly #'my-explain-out))
  (s/check-asserts true)
  (st/instrument))

Nice work! 💯

It’s mostly your work, so thank you 🙂

Can you remove my-explain-out and just incline the custom-printer in this case?

yeah I can, but it gives options 😉

good point 😉

you were right that editing explain-data was a bit more work, so I didn’t do it now. I could walk the tree

I guess I could do the same with the thrown exception

the trick with the thrown exception is that it has already used expound to create the string, so you’d have to do string replacement, which might be unreliable

yes, I would walk both trees. so explain-data before it gets to expound and the one packaged in the ex-info

yeah, could do. in principle you can modify explain-data but you may run into weird expound errors if the explain-data isn’t consistent. I haven’t tried it though

expound doesn’t do anything to protect itself against invalid explain-data. in principle, it could use a spec for explain-data, but I haven’t gotten around to writing one, since it’s not easy 🙂

meh, I think it’s good now. thanks a lot

np, thanks for the helpful questions and feedback!