With the upcoming additions to destructuring I revisted an old bug I reported about destructuring in defprotocol args, to see if the new destucturing directives are affected, and they are. TLDR:
(defprotocol Works (h [this {:keys! [a b c] :as m}])) ;; evals just fine
(defprotocol Errors (h [this {:my-ns/keys! [a b c] :as m}])) ;; throws an unclear spec error
See http://clojure.atlassian.net/browse/CLJ-2657 for detailswhy do you think destructuring forms should work at all in protocol definitions?
why shouldn't they? and if they don't, where is it documented?
it's certainly not documented anywhere that they do
as pure signatures, there is no reason to use destructuring to create bindings
oh shit, i straight up can't read, i'm so sorry. i thought this was about the implementation sites, not the protocol definition. face_with_bags_under_eyes don't mind me
I use destructuring forms to convey which keys implementations of the protocol method can expect,
as a form of documentation.
protocol method definitions already support most destructuring forms now so why not all of them?
Only cases with a slash in the result of (str binding-form) are now not supported due to an implementation detail, which seems like a very arbitrary limitation to me, and the error message you get is not helpful.
IMO it is more of an inconvenience and a bit of an edge case, but the fix is also trivial
But you're not documenting anything real. Nothing that now checks like :keys! will do any checking. Protocols are open and every impl can do different things (and that's sometimes useful!)
And it's very easy for a protocol fn definition to be out of sync with one or more impls even if you want them to be in sync. I just don't think this is a good way to get that documentation.
indeed there is nothing enforcing it
We consider it to be a good practice to wrap a protocol fn with a real fn - that's a great place to do this
that is is indeed what is often do in production code, and also a good place to add validation/spec/asserts
spec is a good example code base where we do this a lot. The protocol is the spi and it's wrapped in fns that are the public api
yes that would be the better pattern and place to document or enforce expectations, but there is still an undocumented limitation and unclear error message. anyway, i agree this is low prio
yes, something more could be done here, but low priority for now
i noticed the recently touted "disable keyword callsites" code was reverted. did y'all find an issue with disabling it?
We expected this would make regular map keyword call site invocation (:foo m) faster at the expense of slower record invocations. Further testing did indeed show that but the loss on records was a bit more than we were willing to bear without further efforts. Still TBD pending more work.
makes sense, thanks
It would be cool to see your benchmarking harness, if that is possible. Or do you compare on macro-benchmarks?
Well it's intentionally very not fancy
I'm asking because with such micro-benchmarks as a hashmap lookup it is very easy to get noise instead of real data. But I won't quibble further:)
Not a micro benchmark
in general, we never use micro benchmarks for anything
here's an example:
(defrecord R [a])
(let [m {:a 1} v (vec (repeat 1000 m))] (dotimes [_ 10] (time (dotimes [_ 100000] (reduce #(+ %1 (:a %2)) 0 v)))))
(let [m (->R 1) v (vec (repeat 1000 m))] (dotimes [_ 10] (time (dotimes [_ 100000] (reduce #(+ %1 (:a %2)) 0 v)))))
Rich has done a bunch of stuff too, so not sure exactly what he's checking
in general, I mostly use stuff in this form of (dotimes [ 10] (time (dotimes [ 1000] ...))) - looking for chunks in the 100s of ms range, and for stable results on repeated runs. in the newer jvms, seeing way more deopt / opt cycles and it's getting harder. depending on the test, I will work harder re blackholing
Pretty reasonable, thanks for the description
sometimes I use criterium, but it does too much stuff and I don't have a lot of confidence in that. sometimes I poke into things with async-profiler, sometimes I look at the jvm inlining / compilation flags if I don't understand what I'm seeing
Given:
user=> #:foo{:x 1 :_/y 2}
{:foo/x 1, :y 2}
would:
(let [{:foo/keys! [x & :_/foo]} {:foo/x 1}])
make sense?
I mean, it's probably dubious, but just asking since some people may expect it given the syntax of namespace maps?seems like a long way to go for a feature no one is even aware of in namespaced maps
:)
several other ways to get that too of course
I think it would be clearer to have :foo/keys! (for the qualified keys/bindings) and also :keys! (for the unqualified keys/bindings).
Ah I think I'm now getting it: {:foo/keys! [x & :_/foo]} would imply :foo/x and :foo are required?
right
but I'm kinda glad that this isn't on the table ;)
But if I understand the thread context, the unqalifying bit does not work right now, and probably never will
yes
โฏ (let [{:foo/keys! [x & :_/foo]} {:foo/x 1 :foo 2}] x)
Missing required key: :_/foo, at user/eval53004 (REPL:1) - runtime error (invalid argument).
Confirmed that the syntax no one knows about doesn't work ๐This is the change between Alpha 9 and Alpha 10 BTW:
โฏ (let [{:foo/keys! [x & :foo]} {:foo/x 1 :foo 2}] x)
Missing required key: :foo/foo, at user/eval53008 (REPL:1) - runtime error (invalid argument).
That "worked" in Alpha 9 because :foo was unqualified.But I think this approach is clearer anyway:
โฏ (let [{:foo/keys! [x] :keys! [& :foo]} {:foo/x 1 :foo 2}] x)
1so unqualified keywords after the & in alpha 10 get auto-qualified to the namespace of .../keys! , and qualified keywords are left unchanged
Maybe people will try to solve Advent of Code puzzles using destructuring only.
> and qualified keywords are left unchanged yes (but double check...) and this is what made me wonder about: how do I get out of namespacing ;)
And you were right to do so. On one hand, it would be more consistent with existing (though rarely-used) syntax. On the other, using multiple :keys reads better
This kind of makes me wonder: why are qualified keywords even allowed there? Wouldn't it be cleaner to require a separate keys declaration for every namespace?
they are in fact, not allowed before the & (never have been), but because after the & is just keys they can be anything
off-topic: the unqualifying namespace in the namespaced map syntax would read funnier as :-/foo
Clojure supports unicode, why even bother with ascii emoticons
but are those valid in a keyword's namespace part?
sure, why not
$ clj
Clojure 1.12.1
user=> (require '[clojure.set :as ๐])
nil
user=> ::๐/join
:clojure.set/join
user=> :๐/join
:๐/joinI specifically added support for this in squint at one point since it didn't work correctly
not that I would recommend it, but some people like it
user=> (require '[clojure.set :as เซช])
Syntax error reading source at (REPL:21:29).
Invalid number: เซช
Syntax error reading source at (REPL:21:30).
Unmatched delimiter: ]
Syntax error reading source at (REPL:21:31).
Unmatched delimiter: )
user=>
ยฏ\(ใ)/ยฏ