In the following fragment "foo" compiles and "bar" doesn't.
// @inlinable public func dropLast(_ k: Int) -> ArraySlice<Element>
// @inlinable public func dropFirst(_ k: Int = 1) -> ArraySlice<Element>
func foo() {
var array = [1, 2, 3, 4, 5]
array = v.dropLast(1)
}
func bar() {
var array = [1, 2, 3, 4, 5]
array = array.dropFirst(1)
// ❌Error No 'dropFirst' candidates produce the expected contextual result type '[Int]'
}
I know that "array subscript" is not array itself and I can do:
array = Array(array.dropFirst(1))
to fix "bar". That's not the question.
I included "dropLast" / "dropLast" prototypes above and they return the same type. Perhaps I am looking at the wrong prototypes and there is some overload for "dropLast" that returns array and no such overload for "dropFirst"? This is further supported by the fact that if I use a separate temporary variable I can break "foo" as well:
let x = array.dropLast(1)
array = x // ❌ Cannot assign value of type 'Array<Int>.SubSequence' (aka 'ArraySlice<Int>') to type '[Int]'
The question is:
why do we have this overload for "dropLast" to begin with?
complexity of "let x = array.dropLast(1)" is known to be O(1). Is complexity of "array = array.dropLast(1)" also O(1)? I presume there's an invisible "array = Array(...)" conversion in there.
if (1) is a good reason, should we have this overload for "dropFirst" as well?
I think the dropLast you're using is the one from Sequence
why do we have this overload for "dropLast" to begin with?
because sequence doesn't have a subsequence type, so it returns Array instead of a subsequence. Array then inherits that overload because of the Collection conformance
complexity of "let x = array.dropLast(1)" is known to be O(1). Is complexity of "array = array.dropLast(1)" also O(1)? I presume there's an invisible "array = Array(...)" conversion in there.
from the docs " Complexity: O( n ), where n is the length of the sequence."
if (1) is a good reason, should we have this overload for "dropFirst" as well?
"dropFirst" for Sequence already exists, and it returns a lazy wrapper which works even for infinite sequences. Returning Array would be worse
Array conforms to both protocols, so without explicit typing, either one can get called, which may be unfortunate. To answer your questions:
The overload exists because this is a useful operation, but Sequences can't be sliced. The only way for the operation to exist is for something to collect the results of iterating the Sequence and drop the last N elements on the floor
No, the Sequence version of the method is O(n), which means that reassigning array = array.dropLast(1) goes through an O(n) copy, which you'll want to avoid. Yes, this is subtle!
It may be possible for the Sequence implementation to optimize this by checking if Self == Array and using some Array internals to avoid a copy via CoW, but I don't know the specifics off the top of my head, and it simply doesn't do this at the moment
We don't need an overload for dropFirst, since Sequence.dropFirst(_:) can already drop the first N element in the sequence (by iterating forward and ignoring the results) in "O(1)" time