Skip to content

RFC for replacing slice::tail()/init() with new methods #1058

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 4 commits into from
Jul 8, 2015

Conversation

lilyball
Copy link
Contributor

Rendered.

A PR for this has already been submitted as rust-lang/rust#24184.

@lilyball
Copy link
Contributor Author

There is some existing discussion in rust-lang/rust#24141 and rust-lang/rust#24184

@nagisa
Copy link
Member

nagisa commented Apr 12, 2015

I really like the tail and init names. shift_* suggest these methods mutate the underlying slice somehow, which is not a case.

@netvl
Copy link

netvl commented Apr 12, 2015

The basic idea is nice, but I personally don't like shift names. They indeed have a mutation connotation. init and tail have pretty widespread meaning. Maybe something like split_tail/split_init would be better?

@Florob
Copy link

Florob commented Apr 13, 2015

I like the general approach. In terms of bike-shedding I think I'd prefer shift_last() to return Option<(&[T], &T)>, since that seems like a more natural order.
Personally I think the shift_* naming is fine, however I'm probably biased since I'm still used to them from the times when slices actually had a lot of those, which are apparently all gone now.

@Ericson2314
Copy link
Contributor

The basic idea is great! I prefer split_ as a prefix, and reversing the tuple for the *last* versions, too.

@lilyball
Copy link
Contributor Author

How do you feel about split_first() / split_last()? I'd really like to move away from the init / tail naming; the methods are already unstable with a comment indicating they're like to be renamed, and they have unwanted historical baggage (expectation of a head method, and panicking on an empty slice instead of returning an optional value).

With the name shift_last() I think Option<(&T, &[T])> makes more sense, as the idea is "remove the first/last element and return it, along with the remainder", but with the name split_last() I agree that Option<(&[T], &T)> would be more sensible ("split" implies a preservation of existing order).

Add the suggested `split_` prefix as a name option, albeit with the
names `split_first()`/`split_last()` instead of the originally-suggested
`split_init()`/`split_tail()`.

Add a question about the return type of `shift_last()`.
@lambda-fairy
Copy link
Contributor

I prefer split_first() / split_last() for the same reasons as everyone else (it doesn't suggest mutation).

I'm aware that the word "shift" appears in slice_shift_char(). But that method isn't named shift_char() -- it has an extra slice prefix tacked on. So I don't think it counts as precedent for a shift_* prefix.

In that vein, I think users will be more familiar with split() and split_at(). So if we take consistency as a goal, then we should be consistent with these methods first.

In fact I'm all for renaming slice_shift_char() to split_first() as well, though I understand if you want to restrict the scope of this RFC.

@iopq
Copy link
Contributor

iopq commented Apr 14, 2015

I feel init() and tail() are inconsistent names because I always look for head() in the API

@blaenk
Copy link
Contributor

blaenk commented Apr 14, 2015

@iopq: If you're looking for head(), then what you want is first(). init() and tail() do have precedent in other languages (not that that should be a reason for keeping them), such as Haskell.

Personally, I do prefer the existing names due to precedent, but I'm fine with the proposed names!

@iopq
Copy link
Contributor

iopq commented Apr 14, 2015

if there is something called first() then I would assume the rest is called rest()

if there is something called head() then I would assume the rest is called tail()
I don't care for mixed metaphors

@blaenk
Copy link
Contributor

blaenk commented Apr 14, 2015

I see what you mean now. I agree.

@rkjnsn
Copy link
Contributor

rkjnsn commented Apr 14, 2015

I like the idea and the return values. I like the single element being returned first in both cases. I have no preference as to shift vs split.

@pnkfelix
Copy link
Member

assigning to @aturon (i.e. to shepherd).

The expression `slice.shift_last().unwrap.1` is more cumbersome than
`slice.init()`. However, this is primarily due to the need for `.unwrap()`
rather than the need for `.1`, and would affect the more conservative solution
(of making the return type `Option<&[T]>`) as well.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a non-sequitur. The idiomatic translation is:

  • slice.tail() -> &slice[1..]
  • slice.init() -> &slice[..slice.len() - 1]

No? Basically init/tail have really just been superceeded by the vastly more flexible slicing syntax (we don't even need to have _mut variants because of slicing, woo!)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't you potentially typo and write &slice[..slice.len()]?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&slice[..slice.len() - 1] only works if you have the slice stored in a variable. If it's the result of some expression, you need to introduce a temporary variable to do that. Which is why I used slice.shift_last().unwrap.1 instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also to note, the &slice[..slice.len()-1] alternative was given up above on line 45, this drawback is only calling out the awkwardness of the shift_last() approach.

@Gankra
Copy link
Contributor

Gankra commented Jun 11, 2015

Misc thoughts:

  • I agree that split_* is perhaps a better name, as this is entirely what we're doing, although it is a bit unfortunate that the semantics with split_at are quite different (panic).
  • I'd like to see an alternative of "just deprecate these" as their current design is almost entirely redundant with slicing (modulo not having to retrieve the len for init)
  • I agree that re-ordering split_last makes sense

I am not super sold on having methods for this overall, but I acknowledge that there's something intrinsically special about the first and last elements in lists.

@alexcrichton
Copy link
Member

This RFC is now entering its week-long final comment period.

@alexcrichton alexcrichton added the final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. label Jul 1, 2015
@jongiddy
Copy link

jongiddy commented Jul 6, 2015

One reason to keep the single element as the first return value for split_last is to easily reuse code with either split_first or split_last. I could imagine selecting FIFO or LIFO order by changing from shift_first to shift_last for example.

@jarcane
Copy link

jarcane commented Jul 6, 2015

first/rest, car/cdr, head/tail are pretty common sequence idioms in other languages. They're a useful pattern, albeit one that is admittedly more useful where recursion is available and efficient.

Furthermore, I can't say as these alternatives seem particularly palatable:

    slice.tail() becomes &slice[1..]
    slice.init() becomes &slice[..slice.len()-1] or slice.shift_last().unwrap().1

Given the choice, I'd choose the former, though a more consistent set of either head/tail, or first/rest, would be a good idea.

@huonw
Copy link
Member

huonw commented Jul 6, 2015

I'd be interested if we could get a bit of a survey of how these functions are used more broadly than just in the main distribution. That said, a quick glance at the github search results... turned up essentially nothing. (I saw one use of tail, but it was in old code and was paired with a head() call, so would be "neater" with the functionality in this RFC anyway.)

These are certainly useful for "triangular" iteration:

fn foo(mut v: &mut [T]) {
    while let Some((head, rest)) = v.split_first_mut() {
        for x in rest { pairwise_mut(head, x) }
        v = rest;
    }
}

(I'm in favour of split instead of shift, because the latter feels like it should mutate, i.e. fn shift_first<'a>(x: &mut &'a [T]) -> &'a T that updates *x to be shorter.)

@alexcrichton
Copy link
Member

The consensus of the libs team was to merge this RFC with the shift namings renamed to split. Could you update this @kballard and then ping me to merge? Thanks!

@lilyball
Copy link
Contributor Author

lilyball commented Jul 8, 2015

@alexcrichton Updated.

@alexcrichton
Copy link
Member

Thanks @kballard!

Tracking issue

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
A-slice Slice related proposals & ideas final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.