Why not make the “forgiving” version the default? I mean, the majority of python-style composable slicing would be happening on arrays and array slices, for which there’s no performance overhead, and the forgiving version would seam to suit the “safe-by-default” philosophy. I’ve seen mistakes like this:
let ar = [1, 2, 3, 4, 5] let arSlice = ar[2..<5] arSlice[1] on a few occasions, for instance. I would think something like this: let ar = [0, 1, 2, 3, 4, 5] let arSlice = ar[2...] // [3, 4, 5] arSlice[..<3] // [2, 3, 4] arSlice[...3] // [2, 3, 4, 5] arSlice[direct: 2] // 2 arSlice[0] // 2 Would be what was expected from most programmers learning Swift, while leaving the unforgiving option open to those who need it. > On 22 Dec 2015, at 03:29, Dave Abrahams via swift-evolution > <[email protected]> wrote: > >> >> On Dec 21, 2015, at 1:51 PM, Kevin Ballard <[email protected] >> <mailto:[email protected]>> wrote: >> >> On Mon, Dec 21, 2015, at 11:56 AM, Dave Abrahams wrote: >>> >>>> On Dec 19, 2015, at 8:52 PM, Kevin Ballard via swift-evolution >>>> <[email protected] <mailto:[email protected]>> wrote: >>>> >>>> On Fri, Dec 18, 2015, at 02:39 PM, Dave Abrahams via swift-evolution wrote: >>>>> >>>>> Yes, we already have facilities to do most of what Python can do here, >>>>> but one major problem IMO is that the “language” of slicing is so >>>>> non-uniform: we have [a..<b], dropFirst, dropLast, prefix, and suffix. >>>>> Introducing “$” for this purpose could make it all hang together and also >>>>> eliminate the “why does it have to be so hard to look at the 2nd >>>>> character of a string?!” problem. That is, use the identifier “$” (yes, >>>>> that’s an identifier in Swift) to denote the beginning-or-end of a >>>>> collection. Thus, >>>>> >>>>> c[c.startIndex.advancedBy(3)] =>c[$+3] // Python: c[3] >>>>> c[c.endIndex.advancedBy(-3)] =>c[$-3] // Python: c[-3] >>>>> >>>>> c.dropFirst(3) =>c[$+3...] // Python: c[3:] >>>>> c.dropLast(3) =>c[..<$-3] // Python: c[:-3] >>>>> c.prefix(3) =>c[..<$+3] // Python: c[:3] >>>>> c.suffix(3) => c[$-3...] // Python: c[-3:] >>>>> >>>>> It even has the nice connotation that, “this might be a little more >>>>> expen$ive than plain indexing” (which it might, for non-random-access >>>>> collections). I think the syntax is still a bit heavy, not least because >>>>> of “..<“ and “...”, but the direction has potential. >>>>> >>>>> I haven’t had the time to really experiment with a design like this; the >>>>> community might be able to help by prototyping and using some >>>>> alternatives. You can do all of this outside the standard library with >>>>> extensions. >>>> >>>> Interesting idea. >>>> >>>> One downside is it masks potentially O(N) operations >>>> (ForwardIndex.advancedBy()) behind the + operator, which is typically >>>> assumed to be an O(1) operation. >>> >>> Yeah, but the “$” is sufficiently unusual that it doesn’t bother me too >>> much. >>> >>>> Alos, the $+3 syntax suggests that it requires there to be at least 3 >>>> elements in the sequence, but prefix()/suffix()/dropFirst/etc. all take >>>> maximum counts, so they operate on sequences of fewer elements. >>> >>> For indexing, $+3 would make that requirement. For slicing, it wouldn’t. >>> I’m not sure why you say something about the syntax suggests exceeding >>> bounds would be an error. >> >> Because there's no precedent for + behaving like a saturating addition, not >> in Swift and not, to my knowledge, anywhere else either. The closest example >> that comes to mind is floating-point numbers eventually ending up at >> Infinity, but that's not really saturating addition, that's just a >> consequence of Infinity + anything == Infinity. Nor do I think we should be >> establishing precedent of using + for saturating addition, because that >> would be surprising to people. > > To call this “saturating addition” is an…interesting…interpretation. I don’t > view it that way at all. The “saturation,” if there is any, happens as part > of subscripting. You don’t even know what the “saturation limit” is until > you couple the range expression with the collection. > > In my view, the addition is part of an EDSL that represents a notional > position offset from the start or end, then the subscript operation > forgivingly trims these offsets as needed. > >> Additionally, I don't think adding a $ to an array slice expression should >> result in a behavioral difference, e.g. array[3..<array.endIndex] and >> array[$+3..<$] should behave the same > > I see your point, but don’t (necessarily) agree with you there. “$” here is > used as an indicator of several of things, including not-necessarily-O(1) and > forgiving slicing. We could introduce a label just to handle that: > > array[forgivingAndNotO1: $+3..<$] > > but it doesn’t look like a win to me. > >> >>>> There's also some confusion with using $ for both start and end. What if I >>>> say c[$..<$]? We'd have to infer from position that the first $ is the >>>> start and the second $ is the end, but then what about c[$+n..<$+m]? We >>>> can't treat the usage of + as meaning "from start" because the argument >>>> might be negative. And if we use the overall sign of the >>>> operation/argument together, then the expression `$+n` could mean from >>>> start or from end, which comes right back to the problem with Python >>>> syntax. >>> >>> There’s a problem with Python syntax? I’m guessing you mean that c[a:b] >>> can have very different interpretations depending on whether a and b are >>> positive or negative? >> >> Exactly. >> >>> First of all, I should say: that doesn’t really bother me. The 99.9% use >>> case for this operation uses literal constants for the offsets, and I >>> haven’t heard of it causing confusion for Python programmers. That said, >>> if we wanted to address it, we could easily require n and m above to be >>> literals, rather than Ints (which incidentally guarantees it’s an O(1) >>> operation). That has upsides and downsides of course. >> >> I don't think we should add this feature in any form if it only supports >> literals. >> >>>> I think Jacob's idea has some promise though: >>>> >>>> c[c.startIndex.advancedBy(3)] => c[fromStart: 3] >>>> c[c.endIndex.advancedBy(-3)] => c[fromEnd: 3] >>> >>>> But naming the slice operations is a little trickier. We could actually >>>> just go ahead and re-use the existing method names for those: >>>> >>>> c.dropFirst(3) => c[dropFirst: 3] >>>> c.dropLast(3) => c[dropLast: 3] >>>> c.prefix(3) => c[prefix: 3] >>>> c.suffix(3) => c[suffix: 3] >>>> >>>> That's not so compelling, since we already have the methods, but I suppose >>>> it makes sense if you want to try and make all slice-producing methods use >>>> subscript syntax (which I have mixed feelings about). >>> >>> Once we get efficient in-place slice mutation (via slice addressors), it >>> becomes a lot more compelling, IMO. But I still don’t find the naming >>> terribly clear, and I don’t love that one needs to combine two subscript >>> operations in order to drop the first and last element or take just >>> elements 3..<5. >> >> You can always add more overloads, such as >> >> c[dropFirst: 3, dropLast: 5] >> >> but I admit that there's a bunch of combinations here that would need to be >> added. >> > > My point is that we have an English language soup that doesn’t compose > naturally. Slicing in Python is much more elegant and composes well. If we > didn’t currently have 6 separate methods (7 including subscript for > index-based slicing) for handling this, that need to be separately documented > and understood, I wouldn’t be so eager to replace the words with an EDSL, but > in this case IMO it is an overall simplification. > >> My concern over trying to make it easier to take elements 3..<5 is that >> incrementing indexes is verbose for a reason, and adding a feature that >> makes it really easy to index into any collection by using integers is a bad >> idea as it will hide O(N) operations behind code that looks like O(1). And >> hiding these operations makes it really easy to accidentally turn an O(N) >> algorithm into an O(N^2) algorithm. > > As I’ve said, I consider the presence of “$” to be enough of an indicator > that something co$tly is happening, though I’m open to other ways of > indicating it. I’m trying to strike a balance between “rigorous” and “easy > to use,” here. Remember that Swift has to work in playgrounds and for > beginning programmers, too. I am likewise unsatisfied with the (lack of) > ease-of-use of String as well (e.g. for lexing and parsing tasks), and have > made improving it a priority for Swift 3. I view fixing the slicing > interface as part of that job. > >>> Even if we need separate symbols for “start” and “end” (e.g. using “$” for >>> both might just be too confusing for people in the end, even if it works >>> otherwise), I still think a generalized form that allows ranges to be used >>> everywhere for slicing is going to be much easier to understand than this >>> hodgepodge of words we use today. >> >> I'm tempted to say that if we do this, we should use two different sigils, >> and more importantly we should not use + and - but instead use methods on >> the sigils like advancedBy(), as if the sigils were literally placeholders >> for the start/end index. That way we won't write code that looks O(1) when >> it's not. For example: >> >> col[^.advancedBy(3)..<$] >> >> Although we'd need to revisit the names a little, because $.advancedBy(-3) >> is a bit odd when we know that $ can't ever take a non-negative number for >> that. >> >> Or maybe we should just use $ instead as a token that means "the collection >> being indexed", so you'd actually say something like >> >> col[$.startIndex.advancedBy(3)..<$.startIndex.advancedBy(5)] > > I really like that direction, but I don’t think it does enough to solve the > ease-of-use problem; I still think the result looks and feels horrible > compared to Python for the constituencies mentioned above. > > I briefly implemented this syntax, that was intended to suggest repeated > incrementation: > > col.startIndex++3 // col.startIndex.advancedBy(3) > > I don’t think that is viable, especially now that we’ve dropped “++” and > “--“. But this syntax > > col[$.start⛄️3..<$.start⛄️5] > > begins to be interesting for some definition of ⛄️. > >> This solves the problem of subscripting a collection without having to store >> it in a local variable, without discarding any of the intentional index >> overhead. Of course, if the goal is to make index operations more concise >> this doesn't really help much, but my argument here is that it's hard to cut >> down on the verbosity without hiding O(N) operations. > > That ship has already sailed somewhat, because e.g. every Collection has to > have a count property, which can be O(N). But I still like to uphold it > where possible. I just don’t think the combination of “+” and “$” > necessarily has such a strong O(1) connotation… especially because the > precedent for seeing those symbols together is regexps. > >> >> -Kevin Ballard >> >>>> But the [fromStart:] and [fromEnd:] subscripts seem useful. >>> Yeah… I really want a unified solution that covers slicing as well as >>> offset indexing. >>> >>> -Dave >>> >> > > -Dave > > > > _______________________________________________ > swift-evolution mailing list > [email protected] <mailto:[email protected]> > https://lists.swift.org/mailman/listinfo/swift-evolution > <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
