StrideTo/Through public properties

StrideTo/Through public properties

  • Proposal: SE–____
  • Author: @Nevin
  • Review Manager: TBD
  • Status: Pitched

Introduction

The StrideTo and StrideThrough types in the standard library have non-public properties _start, _end, and _stride. This proposal introduces public read-only computed properties for them, without the underscores.

Motivation

When working with Stride* instances, it can be useful to access their bounds and stride.

Proposed solution

Add public read-only properties start, end, and stride to the StrideTo and StrideThrough types.

Detailed design

Implementation
extension StrideTo {
  public var start: Element { _start }
  public var end: Element { _end }
  public var stride: Element.Stride { _stride }
}

extension StrideThrough {
  public var start: Element { _start }
  public var end: Element { _end }
  public var stride: Element.Stride { _stride }
}

Source compatibility

Yes.

Effect on ABI stability

No.

Effect on API resilience

These are new public computed properties.

Alternatives considered

  • We could make the properties mutable.
  • We could name the properties differently.
  • We could expand the proposal to include the Stride*Iterator types as well.
  • We could overhaul the design and implementation of the Stride* types in an ABI-incompatible way, to conform them to RandomAccessCollection and make them be their own SubSequences, as proposed 2 years ago in this thread: Proposal to improve the standard library stride types.
4 Likes

I would still like to see this happen. I was bitten again recently by the fact that StrideTo<Int> is not bidirectional or random access, so you can't have a ReversedCollection<StrideTo<Int>>. That's extremely annoying for some bit-shifting operations.

3 Likes

I have some additional ideas for making it easier to use stride types, and I’m wondering if people would prefer one combined proposal or two separate smaller ones.

Building on the ideas from the thread Slicing syntax for math in Swift (tensors), I would like to see a syntax along these lines for striding:

for i in 1..<10.by(3) {
  print(i)  // 1, 3, 7
}

for i in 10...0.by(-5) {
  print(i)  // 10, 5, 0
}

This is easily achieved:

Strideable range shorthand implementation
struct StrideBound<Bound: Strideable> {
  var bound: Bound
  var stride: Bound.Stride
}

extension Strideable {
  func by(_ stride: Stride) -> StrideBound<Self> {
    return StrideBound(bound: self, stride: stride)
  }
  
  static func ... (lhs: Self, rhs: StrideBound<Self>) -> StrideThrough<Self> {
    return stride(from: lhs, through: rhs.bound, by: rhs.stride)
  }
  
  static func ..< (lhs: Self, rhs: StrideBound<Self>) -> StrideTo<Self> {
    return stride(from: lhs, to: rhs.bound, by: rhs.stride)
  }
}

It’s worth noting here that “10...0” by itself would still be invalid: the new functionality is only invoked when the rhs has .by(stride) called on it.

This is a clever solution.

However, I worry that standalone 0.by(-5) would be semantically meaningless but permissible. The method by(_:) would show up under autocomplete for every number, for instance, which seems suboptimal. Also, could there by typechecking performance implications in overloading ... and ..<?

There's no way currently to express ternary operators in the standard library, but I think it'd be reasonable to wonder if this is a circumstance where we would want the real thing, either built into the language or written in the standard library using a new feature.

In general, I agree that a more ergonomic syntax would be nice, but I don't think we need to make compromises in the design when stride(from:to:by:) is serviceable in the meantime.

I think exposing read-only properties is a very reasonable improvement that can stand alone.

Overhauling the types themselves as described earlier to conform to RandomAccessCollection is a worthwhile but much larger orthogonal project, and I think the whole issue about syntax is also another big undertaking.

2 Likes

Thanks!

Yeah, I had that concern too, but then I realized it doesn’t actually matter. Nobody is going to write 0.by(-5) on its own…precisely because it doesn’t do anything useful. Sure, you can write it, you can store it to a variable, you can use that variable as a bound for striding…but why would you?

Yeah, that bugs me too.

I suspect that won’t be much of a problem, because there generally won’t be multiple strides / ranges formed in the same expression.

• • •

These are all valid points to be discussed, however right now I’m just wondering if it’s better to make a single proposal to improve strides in multiple ways, or to split them up into individual tiny proposals.

Right, my two cents: split them up :)

1 Like

This might still be problematic. The search space is roughly correlated with the product of the number of overloads of each operator which appears in the expression. It may be rare to see multiple stride operators in a single expression, but it's much more common for them to appear alongside other heavily overloaded arithmetic operators like + and -. I agree with @xwu that it would probably be better to split up these improvements, I think the public properties as originally pitched is a much less controversial change which would be useful on its own.

I would try to use it in a similar way as PartialRangeFrom, or at least expect it to work that way. Your implementation doesn't support this behaviour, but I think a StrideFrom type could be useful to have.


I think you should split it up (as others have said above). Changing access levels to a few properties seems like a much smaller and easier change to push. Improvements to striding will likely involve large design discussions (if older threads about it are anything to go by).

1 Like

Would you mind pulling this out into a separate pitch? It's independent of your pitch for start/end/stride, and certainly requires a different discussion.

As for adding the pitched properties, the only objection I can think of is that it would provide a means for people to add their own Collection conformance to StrideTo/Through. That is indeed one benefit of making those properties public, but it will complicate things slightly when the standard library is able to add those conformances (which depends at this point on being able to specify availability when adding conformances).

1 Like

I would strongly encourage this approach. Slicing through data is a feature that will only grow in programming languages, and we need to handle it with a clear/simple syntax in Swift too.

The x...y.by(z) is inferior to Python's slicing (and other math packages) at multiple levels:

  • it is a lot less legible. 4- or 5D tensors are pretty common these days (you need to repeat that syntax for each dimension). Slicing through them would require a lot of effort in Swift, and readability would be just bad.
  • Partial definition is missing. Any combination of missing x, y, z values is not well handled (like Python's ::2, or :-1).
  • Negative values for x, y, and z should also be handled.

If this scenario is not obvious to the Swift community (yet), that's fair, even though I believe this will limit the interest for the language in such fields (people pick the one that is the most clear). But what I'd ask in such a situation is that third party operators implementing that behavior be possible, like the one I proposed (x~y~z), which is close but currently has some Swift-related limitations that I'd like to lift.