elvish icon indicating copy to clipboard operation
elvish copied to clipboard

Introduce `get` builtin as a generalized version of `take` and `drop`

Open krader1961 opened this issue 5 years ago • 8 comments

There was a recent discussion thread on IRC/Gitter/etc. about how to take the last N values of an input list. @xiaq commented that they were contemplating adding a get builtin that would support index notation. For example,

  1. get :10 to get the first ten items (equivalent to take 10),
  2. get 10: to skip the first ten items,
  3. get 1:3 to get the second and third items, and
  4. get -10: to fetch the last ten items in the input list.

AFAICT there is no reason the existing take command could not be augmented to support that notation since there is no ambiguity with the existing take syntax. That is, get :10 is equivalent to take 10. Note that take -10 (or any negative count) simply discards the entire input list. I recommend modifying take to treat take -10 as equivalent to get -10:. That is obviously not backward compatible but it seems unlikely to break any existing uses and is conceptually consistent with array slicing.

At the end of the day it doesn't matter very much whether the command is named get or take but I can't see any reason to support both.

krader1961 avatar Jun 24 '20 03:06 krader1961

One possible argument for the get proposal: I assume that the intent is for get to return just one element from the sequence in the absence of a colon, so that get 10 would be equivalent to get 10:11. The latter syntax seems rather clumsy if you wish to get just one. It's tempting, but perhaps too radical, to suggest implementing get as proposed. then simply rewriting take and drop:

fn take [n @items]{ get :$n $@items }
fn drop [n @items]{ get $n':' $@items }

hanche avatar Jun 24 '20 06:06 hanche

I assume that the intent is for get to return just one element from the sequence in the absence of a colon, so that get 10 would be equivalent to get 10:11

Fair enought, but it's not obvious that behavior would be used often enough to justify its existence. You could argue it allows something like get (seq 1 2 10) (equivalent to get 1 3 5 7 9) to extract the first five even numbered items. However, if list indexing where augmented to include an option "step" value then that could be written get 1:10:2.

krader1961 avatar Jun 24 '20 17:06 krader1961

It's already possible to index with multiple expressions; $a[(seq 1 2 10)] extracts the first five evenly numbered items.

I am with @hanche in that get $n should only output a single element, instead of being equivalent to take :$n. After get is implemented, take and drop can be deprecated and eventually removed.

xiaq avatar Jun 24 '20 21:06 xiaq

I agree that the proposed get is preferable to take and drop since the former is a more useful generalization. I think this would be easier to justify if index ranges also supported a step value. For example, if I want every even numbered item from the input list I shouldn't have to know how long the list is as required by the solution using seq. It should be possible to write get 1::2.

krader1961 avatar Jun 25 '20 03:06 krader1961

I'm in full agreement with a general get function, but I would really like to keep take and drop - they are special cases, for sure, but far more readable in the cases in which they apply, and common with many other languages.

It's not much of a difference, but I personally find whatever | take 3 more intuitive than whatever | get :3, even for someone who has never seen the language before.

zzamboni avatar Jun 25 '20 06:06 zzamboni

I think the proposed get should replace take and drop. Although the syntax is maybe a little less obvious, I think it's easier to remember one builtin instead of two.

kolbycrouch avatar Jun 25 '20 20:06 kolbycrouch

It's not much of a difference, but I personally find whatever | take 3 more intuitive than whatever | get :3

@zzamboni, I would argue that replacing take and drop with get reinforces what the user has (or should have) learned about list indexing. Also, while take 3 has the obvious meaning to take the first three values I don't think drop 3 is nearly as obvious. It could mean drop the first or last three values. The list range notation employed by get 3: and get :3 is unambiguous and has the same semantics in the context of list indexing.

Note, too, that get :3 and take 3 are exactly the same length :smile: Which is not an argument in favor of get. I mention it because sometimes conciseness is a reasonable argument but in this case the length of the relevant commands is unimportant.

It might be reasonable to retain take $n as a command implemented as an alias for get :$n but I have a harder time believing that is true for the drop $n example. Looking at the elvish modules I've imported I don't see any uses of take or drop that use anything other than one, 1, as the argument. Either to get the first value or ignore everything but the first value. Whether get 0 or get :1 is clearer than take 1 is debatable. The programmer in me, who is accustomed to zero based indexing and inclusive versus exclusive ranges, has no difficulty using get as an alternative to take and drop. But I am not a representative shell user.

krader1961 avatar Jul 11 '20 04:07 krader1961

Note: For concistency, the colon should be replaced by .., if and when #669 is implemented.

hanche avatar Jul 13 '20 20:07 hanche