Welcome! Please see the About page for a little more info on how this works.

0 votes
ago in Syntax and reader by
edited ago by

Currently, Clojure allows for destructuring patterns that contain &rest arguments:

user=> (let [[a b & c] (range 5)] [a b c])
[0 1 (2 3 4)]

Python allows for the same, but also for placing the so-called catch-all in the front and middle position as well (PEP description):

>>> a, *b, c = range(5)
>>> a
0
>>> c
4
>>> b
[1, 2, 3]

Has similar been considered for Clojure? Could look like this:

user=> (let [[a & b c] (range 5)] [a b c])
[0 (1 2 3) 4]

I feel like this would be doable with how the core functions destructure currently works, but it’s possible I don’t know some subtlety.

1 Answer

+1 vote
ago by
selected ago by
 
Best answer

In what scenario would this be useful?

ago by
reshown ago by
The same or similar scenarios that other sequence destructuring is used:
* I have a sequence of items with some range of data, and I want the bounds (earliest and latest, for example). I could achieve this without any destructuring with [f (first s), l (last s), r (next (butlast s))], but destructuring makes it nicer.
* I have a LIFO queue, and want to select the last item and second to last items to process next: [[& init second-item first-item]]

In each of these scenarios, it’s possible to construct the selected items myself, but that’s also possible with all of Clojure’s destructuring. Using the destructuring syntax is a nice and consistent way to achieve this without filling a let block with all of the busywork.

Alongside that point, by making it a part of the core syntax, it will be usable nested and any performance improvements will be felt by everyone automatically.
ago by
Given sequential (possibly non-indexed) inputs, these kinds of use cases are not generally efficient. Support for this kind of thing is available now in spec (via s/conform and regex specs) for these broader use cases.

In the second case you mention, I think that's not something we'd even want to support or encourage - you're just making a performance mess with something like that.

While I have occasionally seen need for something like this, it's far far rarer than the cases currently supported (and often ill-advised for perf), so I'm not convinced this makes sense to add.
ago by
Makes sense and is roughly the answer I anticipated. Would you be willing to look at a patch to `destructure` with some performance comparisons?
ago by
No, not really interested. I think you underestimate the number of places that assume rest args in the tail position and the effort involved in making a change like this. That combined with a lack of use cases just doesn’t make this something worth spending time on.
ago by
Cool, thanks so much.
ago by
@Noah it could be done as a macro in a library, with pretty much equal community benefit, without getting tangled up in patching clojure.core.  While the language squad is not keen to enshrine slow operations in syntax, programmers also value destructuring for the occasions where it brings clarity.
ago by
@pbwolf Yeah I thought about that too. Maybe I’ll try out out.

@alexmiller After thinking about it, I’m a little confused by your statement that there are many places that assume rest args and the effort that would be required. Is the issue that people rely on the Clojure reader throwing errors if the rest arg is not in the tail position?
ago by
As mentioned above, spec and conform can already handle this if you need it.

(let [{:keys [a b c]} (s/conform (s/cat :a int? :b (s/* int?) :c int?) (range 5))] [a b c])

;;=> [0 [1 2 3] 4]
ago by
I'm talking about in the Clojure implementation there are a lot of things that assume rest args in the tail.
ago by
Ahhh, inside of the Clojure implementation. I thought this was only handled in the `destructure` function. That makes a lot more sense.
Welcome to Clojure Q&A, where you can ask questions and receive answers from members of the Clojure community.
...