Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Completing the Swift 3 range operator suite

Introduction

We propose to complete Swift's suite of range operators to introduce a fully open range (<.<) and the second complementary half-open range (<..).

This proposal was discussed on the Swift Evolution list in the Feature proposal: Range operator with step thread. (Direct link to original thread)

Motivation

At this time, you can build switch pattern matching cases for "low <= x <= high" and "low <= x < high" but you cannot create corresponding cases for "low < x <= high" or "low < x < high". Updating Range for Swift 3 offers a window of opportunity to complete the range operators for use in pattern matching, numerical applications, and teaching scenarios. By comparison, Perl now supports four range operators: .. (equivalent to Swift's ...), ..^ (equivalent to Swift's ..<), ^.. (lower bound excluded, no Swift equivalent), and ^..^ (both bounds excluded, no Swift equivalent).

Current Swift 3 plans include updates to Range that subsume the functions of Interval. A Range in Swift 3 will not necessarily represent a discrete sequence of values but could also represent a continuous interval with floating point bounds. For two integer values a and b, a ... b == a ..< (b + 1). By contrast, for two floating point values c and d, the closed range c...d cannot trivially be expressed as a half-open range without breaking the abstraction that floating point types are intended to represent real values.

For numerical purposes in the educational setting or in the context of scientific computing (among other use cases), it is sometimes necessary to represent intervals where the lower bound, upper bound, or both are open. Although Range offers methods such as contains and clamp applicable to all intervals, Range types only exist at the moment that represent intervals including the lower bound. Concordantly, only the operators ... and ..< exist in Swift. These operators are sufficient (as are any one of the two) for representing sequences of discrete values but they are insufficient for expressing all types of bounded ranges once floating point types (and, in the future, rationals, pattern matching, etc.) are included.

Detailed Design

Adopt the operators <.. and <.< to represent ranges that exclude the lower bound or both bounds, respectively. The correspondingly named types (or a single Range type with Bool properties to indicate bound inclusion or exclusion) must accompany the introduction of these operators.

operator meaning Swift 3
... low <= x <= high yes
..< low <= x < high yes
<.. low < x <= high proposed
<.< low < x < high proposed

Sample use cases:

Current:

for x in [0, 1, 2, 3, // 1st case
          4, 5, // 2nd case
          8, 9, // 3rd case
          11, // 4th case
          6, 7, 10, 12 // no match
          ] {
    switch x {
    case 0...3:
        print("0 <= \(x) <= 3")
    case 4..<6:
        print("4 <= \(x) < 6")
    case let y where y > 7 && y <= 9:
        print("7 < \(x) <= 9")
    case let y where y > 10 && y < 12:
        print("10 < \(x) < 12")
    default: print("No match for \(x)")
    }
}

Proposed:

for x in [0, 1, 2, 3, // 1st case
          4, 5, // 2nd case
          8, 9, // 3rd case
          11, // 4th case
          6, 7, 10, 12 // no match
          ] {
    switch x {
    case 0...3:
        print("0 <= \(x) <= 3")
    case 4..<6:
        print("4 <= \(x) < 6")
    case 7<..9:
        print("7 < \(x) <= 9")
    case 10<.<12:
        print("10 < \(x) < 12")
    default: print("No match for \(x)")
    }
}

Design concerns

This proposal may require that Swift's current operator naming rules be revised with regard to embedded dots. Under the current rules, embedded dots are disallowed in custom operators. Although these new operators would not be "custom" and the rule might not apply, its certainly an issue that would need special consideration in expanding the language and adapting the compiler:

You can also define custom operators that begin with a dot (.). These operators are can contain additional dots such as .+.. If an operator doesn’t begin with a dot, it can’t contain a dot elsewhere. For example, +.+ is treated as the + operator followed by the .+ operator. cite: Swift Programming Language

Alternatives Considered

None

Acknowlegements

Thanks, Dave Abrahams

@pyrtsa

This comment has been minimized.

Copy link

commented Apr 7, 2016

Should we clarify what are the concrete types returned by these operators, given bounds of type T (Int, Double, others)?

As of Swift 2.x, we have:

struct Range<Element : ForwardIndexType>
    : SequenceType, CollectionType, Equatable, Indexable, ... // plus some less relevant protocols
struct ClosedInterval<Bound : Comparable> : Equatable, IntervalType, ...
struct HalfOpenInterval<Bound : Comparable> : Equatable, IntervalType, ...

Currently, given Int arguments to ..< and ..., both always return Range<Int>. My vague understanding is that in the long run, we'd want to get rid of the *Interval types and only have different ranges that conditionally conform to CollectionType (or "Collection") if their bounds happen to be enumerable. (Obviously, before more work on the generics system, that's currently impossible.) But should we address that topic somehow?

@dabrahams

This comment has been minimized.

Copy link

commented Apr 7, 2016

nit: s/Detail Design/Detailed Design/

@erica

This comment has been minimized.

Copy link
Owner Author

commented Apr 7, 2016

@dabrahams fixed

@Pyroh

This comment has been minimized.

Copy link

commented Apr 13, 2016

I know it would imply the use of a new literal instead of operators but ranges could be written in a more mathematical way: with brackets.
The only difference would be inability to use the coma as separator between low and high value.

Let's make it clearer. What is 1...3 in Swift is [1,3] in maths. As soon as [1,3] is an array literal in Swift we can't use it verbatim. I propose using something like [1..3]. And now it is as easy to express open, semi-open and closed ranges:

literal meaning
[a..b] low <= x <= high
[a..b[ low <= x < high
]a..b] low < x <= high
]a..b[ low < x < high

Plus it is faster to write [1..3].reverse() than writing 1...3.reverse(), realising you forgot the parenthesises and finally adding them. Or even worse writing 1...3., waiting for autocompletion to appear and finally not being able to find reverse() (we all do, don't we ?).

We can also imagine a handy RangeLiteralConvertible protocol:

// array = [1, 2]
let array: [Int] = [1..3[

// set = [4, 5]
let set: Set<Int> = ]3..5]

I guess adding a range literal to the language means a lot of work but it could be a nice evolution.

@jcnm

This comment has been minimized.

Copy link

commented Apr 19, 2016

As suggested by Pyroh doesn't it make more sense to use brackets for intervales? Does handle these tokens with array literal tokens is far too complicated?

Even if using comparison symbols could make sense as described in the proposal, people are familiar with the bracket approach.

The form doesn't matter for me but Swift 3.0 need these operators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.