rename dropFirst() and dropLast()

Hi all,

Today I made a mistake trying to use .dropFirst() to remove an array element in-place. I have the feeling that I will second-guess myself the next time.

IHMO, now that dorpFirst() is no-longer a free function, it's name is hardly distinguishable from .removeFirst(). When compared side-by-side, .dropFirst() does seem to suggest “return the rest". But this subtlety is easily lost for users who don’t use both on regular bases. It’s worse for non-native English speakers.

Looking at a few other languages that provides a .dropFirst() for their list/array/sequence:

* Haskell’s “tail" only makes sense when paired with “head" (and not that self-explanatory).
* Rust doesn’t have a direct counterpart, but split_first() return both head and tail in a tuple.
* Scala and Ruby has a .drop(), but there’s no equivalent of .removeFirst() to cause confusion.

There must be a better name for this important method. I came up with:

.dropFirst() => .excludeFirst()

What do y’all think?

(Everything above applies to .dropLast())

- Daniel

Following the new API guidelines, shouldn't the name be either `droppingFirst` or `excludingFirst`? That would solve your problem, right? Many things still need to be adapted to the new conventions.

https://swift.org/documentation/api-design-guidelines.html#be-grammatical

2 Likes

That's not what the conventions say. They say non-mutating phrases should read as noun phrases when possible. But `droppingFirst` isn't a noun phrase. This means things like `index.successor()`. It also says that if there is no good noun phrase to use then imperative verbs are acceptable, which means it will stay as `dropFirst`.

Hi Michel,

Indeed, the guideline clarifies! .excludingFirst() is better :)

- Daniel

1 Like

Really? The convention says: "When a mutating method is described by a verb, name its non-mutating counterpart according to the “ed/ing” rule, e.g. the non-mutating versions of x.sort() and x.append(y) are x.sorted() and x.appending(y)."

Are you telling me that because no one bothered *yet* writing a mutating version of this method the "ing" rule does not apply? That it will apply only the day someone will write mutating method called `dropFirst`? This interpretation of the guidelines does not make much sense.

1 Like

Oh my bad, you're right. I read the first two rules, glanced ahead and saw it talking about other specialized variants (e.g. boolean methods/properties) and managed to not notice what the third rule said.

That said, `droppingFirst` sounds pretty weird to me. "drop" (and the related verb "take" that we're not using) has precedent in multiple languages (Rust and Haskell come to mind) to mean "return a new sequence that skips the first N elements". And I'm not aware of any language that sets precedent for the verb "drop" to mean "mutate the receiver".

To add to this, existing Swift and Obj-C precedent says the mutating method will be named with "remove" (e.g. `removeFirst`). So the logical name for this based on the grammar rule is `removingFirst`, but that also sounds weird to me.

That’s really ironic, given that sort() on Array in the standard library is the non-mutating version (the mutating version is called sortInPlace).

Charles

Sounds fine to me, but I'll admit not being a native English speaker. I know I would never have thought looking for `drop` when looking for the non-mutating version. Seems I'm not familiar enough with Rust and Haskell. It's a certainly a nice thing that if I began by writing `remove`, autocompletion would have suggested `removing`: no guesswork with synonyms to find the non-mutating variant.

Hmm, I just took a look, and while Rust does use "take", it actually doesn't use "drop" (but Haskell does). Instead it uses "skip", which seems like a good candidate if we're going to rename this. I'm tempted to say we should use "take" instead of "prefix" as well, because `seq.prefix(3)` isn't actually immediately obvious what it does (as the verb "prefix" usually means to add onto the front, not to take the front). And we can use "takeLast" for "suffix" (neither Rust nor Haskell appears to have an equivalent of takeLast; I believe Rust doesn't because none of its iterator adaptors use dynamically-allocated memory, and I think Haskell expects you to just do `reverse . take n . reverse`). Although I do notice Haskell has a function dropWhileEnd that drops the suffix, which suggests "takeEnd" and "dropEnd" here.

Which is to say, if we're going to rename these methods, my vote is:

prefix -> take
suffix -> takeEnd or takeLast
dropFirst -> skip
dropLast -> skipEnd or skipLast

If one were to rename dropFirst/dropLast, I’d love for prefix and suffix to be renamed also to better pair with the methods.

Something like:
- absorbingFirst
- discardingFirst
- disregardingFirst
- droppingFirst
- eliminatingFirst
- excludingFirst
- filteringFirst
- ignoringFirst
- refusingFirst
- removingFirst

paired with an alternate name for “prefix”:
- acceptingFirst
- choosingFirst
- harvestingFirst
- includingFirst
- outputtingFirst
- pickingFirst
- selectingFirst
- takingFirst

Alternatively (and perhaps clearer) once could take “prefix” and pair it with a prefix-dropping method:
- absorbingPrefix
- discardingPrefix
- disregardingPrefix
- droppingPrefix
- eliminatingPrefix
- excludingPrefix
- filteringPrefix
- ignoringPrefix
- refusingPrefix
- removingPrefix

My vote? “prefix” and “ignoringPrefix” paired with “suffix” and “ignoringSuffix”. Words like ‘absorb’, ‘disregard’ and ‘ignore’ seem to better indicate what the default algorithm is going to do.

(Of course with that logic, “suffix” is a terribly misleading name w.r.t. complexity and memory usage, when you consider what the generator-backed implementation has to do)

-DW

Hi Lily,

“take" and “skip” are fine as free function names. As method names, they are a step back from following the API Guidelines (“non-mutating methods should read as noun phrases”).

- Daniel

I prefer to address this by creating a unified EDSL for describing all of these operations as discussed in this thread: Re: Proposal: Python's indexing and slicing <http://news.gmane.org/find-root.php?message_id=ED492767-E1ED-41D6-90A8-BCA9AC6D0F34%40apple.com&gt;

-Dave

Charles,

Guidelines are being applied to the Standard Library as part of Swift 3 effort. You can track progress in `swift-3-api-guidelines` branch.
Addressing your example: non-mutating `sort()` has become `sorted()` here <https://github.com/apple/swift/blob/swift-3-api-guidelines/stdlib/public/core/CollectionAlgorithms.swift.gyb#L213&gt;\.

max

I guess that's a good argument for keeping "prefix" and "suffix" instead of "take" and "takeEnd". But there is no good noun phrase to use for dropFirst/dropLast (Haskell's "init" and "tail" are nouns but they're very confusing and don't really make sense once you add in an integral argument anyway). The guidelines do say it's acceptable to use an imperative verb if there is no good noun phrase, so "skip" and "skipEnd" (or "skipLast", or maybe "skipSuffix" if we're keeping "suffix") are still reasonable.

Incidentally, it occurs to me that "removingFirst" is actually not an appropriate name here, because dropFirst is a method of SequenceType, and SequenceType does not have mutating methods. removeFirst is actually defined by RangeReplaceableCollectionType (and by Set, and also by CollectionType if SubSequence == Self).

I do rather like that, although the thread stalled a week ago which is equivalent to 1 swift-evolution year :-)

Is the current thinking that “$ + 3” and its ilk be a literal or computation returning some sort of Offset or Slice value, and SequenceType would define a subscript operation taking a Slice and returning an Element or SubSequence respectively?

-DW

I guess that's a good argument for keeping "prefix" and "suffix" instead of "take" and "takeEnd". But there is no good noun phrase to use for dropFirst/dropLast (Haskell's "init" and "tail" are nouns but they're very confusing and don't really make sense once you add in an integral argument anyway). The guidelines do say it's acceptable to use an imperative verb if there is no good noun phrase, so "skip" and "skipEnd" (or "skipLast", or maybe "skipSuffix" if we're keeping "suffix") are still reasonable.

I'm thinking:

  collection.onlyFirst(5)
  collection.exceptFirst(5)
  collection.onlyLast(5)
  collection.exceptLast(5)

Perfectly parallel, don't sound like mutating operations, and very clear about which part you keep and which part you toss.

I made the same observation on Haskell in the original post :)

“removeFirst()” is also defined in CollectionType. So “dropFirst()” is removeFirst()’s – in the Guidelines’ term – "non-mutating counterpart" for all CollectionTypes. Therefore, “removingFirst()” would be the winner.

(My personal preference notwithstanding, that is).

I do rather like that, although the thread stalled a week ago which is equivalent to 1 swift-evolution year :-)

Is the current thinking that “$ + 3” and its ilk be a literal or computation returning some sort of Offset or Slice value, and SequenceType would define a subscript operation taking a Slice and returning an Element or SubSequence respectively?

Yes, something like that.

Actually that's not true, CollectionType does not define dropFirst(). It merely provides a default implementation. dropFirst() is only actually defined in SequenceType, which does not have any mutating methods. Renaming it to "removingFirst()" would be against the guidelines for all sequences that are not also RangeReplaceableCollectionTypes.