yeah you’re probably right. you’d still need some way of using the Int
parameter to generate the inline storage though. Without it it’s basically a macro that changes a static variable inside the type and we can already do that in the current protocol system
Constant as in, values instead of types?
Like Tuple<n: UInt>
?
Because... that would be awesome!
Yes
Omg. So much type-level fun to have! Whenever that arrives, I’m gonna be a happy man :)
The only reason I gave visibility of nodes was to let the people know what kind of type is being used for storing an element behind the scenes. Of course they would only be able to manipulate it from the collection itself.
By the way, "move semantics" make much more sense. I have taken a look at your design and it seems good. The naming of mutating func trade(my subrange: Range<Index>, forNodesOf other: inout Self, along otherSubrange: Range<Index>) -> (myNewNodes: Range<Index>, otherNewNodes: Range<Index>)
seems a little complex though. Also I think the protocol should be named something like TransferableCollection
if using this approach.
Which is a form of juggling nodes around:
extension LinkedNodeCollection {
/**
Detaches and returns a suffix, breaking where the prefix would no longer match a running sort.
The first two elements are checked if they are sorted in respect to the given predicate. If so, the check continues with the second and third elements, then the third and fourth elements, and so on. At the first pair where the later element isn't properly ordereed with respect to the earlier element, the suffix starting with that later element is detached and returned.
- Parameter areInIncreasingOrder: A predicate that returns `true` if its first argument should be ordered before its second argument; otherwise, `false`.
- Returns: The suffix of `self`, or `nil` if the entirety of `self` is sorted with respect to `areInIncreasingOrder`. (An instance with less than two elements always returns `nil`.)
- Postcondition: `self` is sorted with respected to `areInIncreasingOrder`, without any "equivalent" elements. Further calls with the same predicate will return `nil`. In other words, `zip(self.dropLast(), self.dropFirst()).map { areInIncreasingOrder($0.0, $0.1) }.elementsEqual(repeatElement(true, count: max(0, self.count - 1)))`.
*/
public mutating func purgeSuffixMissorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Self? {
var lastIncluded = startIndex
for i in indices.dropFirst() {
if try !areInIncreasingOrder(self[lastIncluded], self[i]) {
return truncate(after: lastIncluded)
}
lastIncluded = i
}
return nil
}
/**
Sorts the collection in place via storage-shuffling, using the given predicate as the comparison between elements.
The sort is stable.
- Parameter areInIncreasingOrder: A predicate that returns `true` if its first argument should be ordered before its second argument; otherwise, `false`.
- Postcondition: `self` is sorted with respected to `areInIncreasingOrder`.
*/
public mutating func mergeSort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows {
// Check if this instance is already sorted.
guard var missortedSuffix = try purgeSuffixMissorted(by: areInIncreasingOrder) else { return }
// Recursively sort.
try missortedSuffix.mergeSort(by: areInIncreasingOrder)
// Merge. Using the suffix collection as the first argument should make equivalent operands choose the element from this instance first.
var sorted = try Self(merging: &missortedSuffix, and: &self, admitFirstOverSecond: areInIncreasingOrder)
tradeShifted(my: (nil, endIndex), forNodesOf: &sorted, along: (nil, sorted.endIndex))
}
}
extension LinkedNodeCollection where Element: Comparable {
/**
Detaches and returns the suffix where the sequence first stops increasing.
- Returns: The suffix of `self`, or `nil` if the entirety of `self` is already strictly increasing.
- Postcondition: `self` is a strictly increasing sequence.
*/
public mutating func purgeNonincreasingSuffix() -> Self? {
return purgeSuffixMissorted(by: <)
}
/**
Sorts the collection in place, shuffling the elements' storage instead of mutating said elements.
You can sort any element-transferable collection of elements that conform to the `Comparable` protocol by calling this method. Elements are sorted in ascending order. The sorting is stable; equal elements keep their relative order in this collection.
To sort the elements of your collection in descending order, pass the greater-than operator (`>`) to the `mergeSort(by:)` method.
- Postcondition: `self` is sorted in non-decreasing order.
*/
public mutating func mergeSort() {
mergeSort(by: <)
}
}
I think that counts as O(1) memory management; I don't create any new nodes. I hope using a missorted suffix, which acts as a natural merge sort, counts as a smart scan.
Is it OK that the Comparable
version of missorted splitting uses a different name?
I think the sort is stable, by choosing the suffix nodes to go first. But I wouldn't mind anyone double-checking.

I’m not a fan of lists for most things, but they do have specific technical advantages that are killer features in some domains. For example, the iterator invalidation behavior is a killer feature for compiler IRs. That said, I’d be the first to admit that this is niche, and lists are not difficult to roll your own when you need them.
OTOH, the challenge of implementing them in swift is that you really don’t want to use strong pointers for forward and back pointers (you want unsafe pointers between the nodes and the list to handle their ownership).
Maybe there is a more primitive concept that would be possible to build and expose, allowing users to build their own efficient lists out of that underlying concept?
What do you mean by iterator's invalidation behavior? Is it that moving nodes around shouldn't invalidate indexes/references to nodes unaffected by the move. (For example, if I remove a range out of a list, but keep index values for the elements corresponding to the nodes immediately before and after the range, those index values should still work after the removal.) I've implicitly been expecting that, but I don't know if I should insist on it in the protocol (description).
For the "more primitive" concept, do you mean like a protocol? Like the sample protocol from my previous message? Or are you talking about the techniques that would be used within the implementation of trade
and/or tradeShifted
?

Seriously. My forthcoming constexpr proposal is an important piece to providing constant arguments for generics, which may be the path forward for fixed size arrays, the essential ingredient we’re missing to be able to implement a SmallVector-like type in Swift.
For "SmallVector
," you mean the one for LLVM? I guess that could be:
enum SmallVector<Element, let Threshold: Int> {
case small(elements: [Threshold; Element?], useCount: Int)
case large(elements: [Element])
init<S>(_ elements: S) where S: Sequence, S.Element == Element {
let array = Array(elements)
if array.count > Threshold {
self = .large(elements: array)
} else {
self = .small(elements: createArray {
$0.0 < array.count ? array[$0.0] : nil
}, useCount: array.count)
}
}
}
// Insert extension for whatever combination of MutableCollection, RandomAccessCollection, and/or RangeReplaceableCollection you want.
Where createArray
is a Standard Library global function:
func createArray<Element, let Extents: Int pack>(_ initializer: ([#count(Extents); Int]) throws -> Element) rethrows -> [#explode(Extents); Element]

Yes, it’s most likely beneficial, but that’s also true for sorted arrays, various flavors of trees and other structures - and imho Swift just doesn’t have a good place to put those:
It’s already to specific for the stdlib
To be clear: there is no rule anywhere that data structures more specialized than the current Array
/Dictionary
/Set
are too specific for the standard library. I even raised a jira to add sorted collections to the standard library, and would love for someone to pick that up and run with it.
Whether a linked list, however, is the right mix of generally useful vs difficult to implement in order to qualify for std lib inclusion is debatable (and being ably debated in this very thread :)
This sounds great! I was thinking about bringing back up 0074, but solving it by using a more general sorted collection protocol. I mulled over it a while, but decided not to since I wasn’t sure their would be any benefits for it over say a sorted/ordered set performance wise. Am I missing a use case for sorted collection over said sets?

To be clear: there is no rule anywhere that data structures more specialized than the current Array/Dictionary/Set are too specific for the standard library. I even raised a jira to add sorted collections to the standard library, and would love for someone to pick that up and run with it.
Wait a moment... does that mean somebody could just write a simple SortedArray, and get his work into the stdlib?
My impression is that there many eager contributors who just don't have the time or knowledge to cope with C++ and llvm, so finding a volunteer for a pure Swift problem should be rather easy ;-)

Wait a moment… does that mean somebody could just write a simple SortedArray, and get his work into the stdlib?
It's not quite that simple :) A first step would be gathering justifications, putting together a good candidate implementation, and pitching that to the community. And it would probably need to be part of a wholistic consideration of sorted containers and algorithms on sorted input. That task shouldn't be taken lightly.
But it's definitely not the case that such a proposal would be ruled out-of-scope for the std lib from the very outset.

My impression is that there many eager contributors who just don’t have the time or knowledge to cope with C++ and llvm, so finding a volunteer for a pure Swift problem should be rather easy ;-)
the coding is the easy part, the bureaucracy is the hard part

the coding is the easy part, the bureaucracy is the hard part
just to clarify: My "Like" just means I agree with the statement - I don't actually like that getting non-trivial changes through evolution is as tedious as hand coding the implementation in assembler ;-)

just to clarify: My “Like” just means I agree with the statement - I don’t actually like that getting non-trivial changes through evolution is as tedious as hand coding the implementation in assembler ;-)
it’s a fact of life that no proposal shall pass the swift evolutions without extensive bikeshedding over support for arbitrary-precision booleans

without extensive bikeshedding over support for arbitrary-precision booleans
How I wish we could have Maybe
, Probably
, Surely
booleans!
But the arbitrary precision really comes from how you compose them. Like when things are ocurring Surely x Surely - Maybe
so often <3

How I wish we could have Maybe, Probably, Surely booleans!
But the arbitrary precision really comes from how you compose them. Like when things are ocurring Surely x Surely - Maybe so often <3
what we really need is something that can represent arbitrarily fine-grained quantities between 0 (false) and 1(true). such a thing could be immensely useful for example, in probability applications, where we need some way of expressing uncertainty… if only there was a type you could use to express fractional quantities,, but with the binary point fixed after the 0.
… like some sort of FixedFloatingPoint
…
of course, we’d still need to work out how to support the literal initializers. currently ExpressibleByBooleanLiteral
only takes a a standard 1-bit Bool
. I suspect this will only be possible after Swift supports variadic generics though

If you’re interested in this sort of thing, I’d encourage thinking about how to implement intrusive linked lists. They really do provide benefits, because you can contain heterogenous elements. We use these pervasively for the compiler representations in LLVM/Swift:
LLVM Programmer’s Manual — LLVM 18.0.0git documentation
I’m haven’t thought much about what the best way to make a generic intrusive list is in Swift.
What about starting with a protocol:
protocol IntrusiveListNode: AnyObject {
var previousNode: IntrusiveListNode? { get set }
var nextNode: IntrusiveListNode? { get set }
}
But don't see any way to stop anyone who has access to a node's payload from also doing arbitrary link surgery. Are you expecting the users to just have enough discipline to not do that?
I also got a warning that adding "weak
" or "unsafe
" is going away. I don't see how to prevent reference counting and retains. Unless we ask the node to store OpaquePointer
or similar, and take care of retention counts in the list type.
I don't have a good sense for what will work here. I think the best approach here is to go off and build something, solve for various specific domain problems, and come up with something that works well across multiple common problems. Once you're feeling good about a design and the tradeoffs it encompasses, it would make sense to summarize what you've learned and then potentially put together a proposal.
-Chris
What about these names and interface:
public protocol PoorlyTransferableCollection: RangeReplaceableCollection where Self.SubSequence: PoorlyTransferableCollection {
mutating func tradeOver(_ firstRange: InvertedRange<Index>, _ secondRange: InvertedRange<Index>) -> (newFirstRange: InvertedRange<Index>, newSecondRange: InvertedRange<Index>)
mutating func trade(_ subrange: InvertedRange<Index>, with other: inout Self, along otherSubrange: InvertedRange<Index>) -> (myNewRange: InvertedRange<Index>, otherNewRange: InvertedRange<Index>)
}
extension PoorlyTransferableCollection {
public mutating func tradeOver(_ firstRange: InvertedRange<Index>, _ secondRange: InvertedRange<Index>) -> (newFirstRange: InvertedRange<Index>, newSecondRange: InvertedRange<Index>) {
var intermediate = Self()
let intermediateFirstRange = trade(firstRange, with: &intermediate, along: .all).myNewRange
let finalSecondRange = trade(secondRange, with: &intermediate, along: .all).myNewRange
let finalFirstRange = trade(intermediateFirstRange, with: &intermediate, along: .all).myNewRange
return (finalFirstRange, finalSecondRange)
}
}
extension PoorlyTransferableCollection {
public mutating func segregate(out subrange: InvertedRange<Index>) -> Self
public mutating func truncate(upThrough i: Index) -> Self
public mutating func prepend(stealingFrom other: inout Self)
public mutating func truncate(after i: Index) -> Self
public mutating func insertNodesAfter(i: Index, stealingFrom other: inout Self)
public mutating func append(stealingFrom other: inout Self)
public mutating func filterOut(by willBeRemoved: (Element) throws -> Bool) rethrows -> Self
public mutating func arrangePartitioning(by belongsInSecondPartition: (Element) throws -> Bool) rethrows -> Index
public init(merging first: inout Self, and second: inout Self, admitFirstOverSecond body: (Element, Element) throws -> Bool) rethrows
public mutating func purgeSuffixMissorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Self?
public mutating func mergeSort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows
}
extension PoorlyTransferableCollection where Element: Comparable {
public mutating func purgeNonincreasingSuffix() -> Self?
public mutating func mergeSort()
}
protocol TransferableCollection: PoorlyTransferableCollection where Self: BidirectionalCollection, Self.SubSequence: TransferableCollection { /*...*/ }
tradeOver(_:_:)
exists for the same reason that MutableCollection.swapAt(_:_:)
does; you can't call a function that mutates an object twice, so putting self
in trade(_:with:along:)
would violate mutation safety. Is there still a problem with the interface complexity? I'm going to insist that conforming types not invalidate the indexes of uninvolved elements (in tradeOver
, trade
, and replaceSubrange
), but you still need to know the indexes of the newly moved elements somehow, and I don't want to force the user to instead track the neighboring elements to infer the new bounds.
A half-closed range is defined as:
public enum InvertedRange<Index: Comparable> {
case beforeStart
case prefix(upThrough: Index)
case range(after: Index, upThrough: Index)
case suffix(after: Index)
case afterEnd
case all
}