简体   繁体   中英

Is there a standard argument sequence identity function in Clojure?

Is there a function in the Clojure standard library equivalent to the following?

(fn [& args] args)

If not, why?

Example usage:

(take 10 (apply (fn [& args] args) (range)))
;=> (0 1 2 3 4 5 6 7 8 9)

;; ironically, map isn't lazy enough, so let's take it up to 11
(defn lazy-map [f & colls]
  (lazy-seq (cons (apply f (map first colls))
                  (apply lazy-map f (map rest colls)))))

(defn transpose [m]
  (apply lazy-map (fn [& args] args) m))

(defn take-2d [rows cols coll]
  (take rows (map (partial take cols) coll)))

(take-2d 3 3 (transpose (map (partial iterate inc) (range))))
;=> ((0 1 2) (1 2 3) (2 3 4))

Please note that I am not asking for a transformative, eager function such as vector or list .

There is no such function, you are free to implement and use it as you like:

(defn args [& args] args)

(set (map type (apply map args [[1 2 3][4 5 6][7 8 9]])))
=> #{clojure.lang.ArraySeq}

Why isn't it already available?

This is rarely fruitful question: not only we don't know what happens in the mind of implementors, it is impracticable to ask them to justify or document why they did not do something. Was adding this function ever considered? How can we know? Is there really a reason, or did it just happen?

On the other hand, I agree that args feels simpler because it passes around an already existing immutable sequence. I also can understand if you think that not converting the arguments as a persistent list in the first place is better, if only for the sake of parcimony.

But this is not how it is implemented, and the overhead of using list is really negligible (and specialized when building from an instance of ArraySeq ). You are supposed to code to an interface and never look behind the curtain, and from this point of view, list and args are equivalent , even though they do not return identical results.

You added a remark about laziness, and you are right: if you ever need to take the arguments from a variadic function and pass it to a function which operates on sequences, The version with list will consume all the given arguments whereas args will not. In some cases, as with (apply list (range)) , where you literally pass an infinite number of arguments, this might hang forever.

From that point of view, the little args function is in fact interesting: you can move from arguments to actual sequences without introducing potential problems. I am however not sure how often this case happens in practice. In fact, I have a hard time finding a use case where laziness in the argument list really matters as far as args is concerned. After all, in order to pass an infinite sequence, the only way (?) is to use apply:

(apply f (infinite))

In order to have a use-case for args , that means that we want to convert from an argument list back to a single list so that another function g can use it as a sequence, like so:

(g (apply args (infinite)))

But in that case, we could directly call:

(g (infinite))

In your example, g would stand for cons inside lazy-map , but since f is given in input, we cannot write (cons (map ...) ...) directly. The example thus looks like a genuine use case for args , but you should document the function heavily because the snippet you gave is quite complex. I tend to think that giving an unbounded number of arguments to a function is a code smell: should every function with an [& args] signature avoid consuming all arguments because the given sequence might in fact be infinite, like lazy-map does? I'd rather have a single argument be a lazy sequence for this kind of usage (and pass identity where needed) instead of the whole argument list, to clarify the intent. But in the end, I am not strongly opposed to the use of args either.

So to conclude, unless you manage to convince Rich Hickey to add args as a core function, I am confident that almost nobody will want to depend on an external library which does just this 1 : it is unfamiliar, but also trivial to implement and mostly useless. The only reward is knowing that you skip a little transformation step which costs nothing in most cases. Likewise, do not worry about having to choose between a vector and a list: it has practically no influence on your code and you can still modify the code later if you can prove it is necessary. Regarding laziness, while I agree that wrapping arguments in lists or vectors can be problematic with unbounded argument lists, I am not sure the problem actually arises in practice.


1 . Of course, if it ever reaches clojure.core , everybody will be quick to say that is is a fundamental operation which is most useful and definitely idiomatic </cynic>

There's identity function. It takes an argument and just returns that argument. Clojure's identity is single arity though.

usage:

(identity 4) ;=> 4

(identity [1 2 3 4]) ;=> [1 2 3 4]

I don't think there's much sense in having the identity function with variable arity since Clojure functions return only one value. If you want to return multiple values from a function, then you can wrap them in a seq which you can later destructure. In that case you can have something like this:

(defn varity-identity [& args]
    (map identity args))

(varity-identity 1 2 3 4 5) ;=> (1 2 3 4 5)

Hope this helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM