Ranges for enum and struct types
Swift has a syntax for ranges of values:
0...5 // represents [0, 1, 2, 3, 4, 5]
0..<5 // represents [0, 1, 2, 3, 4]
(0...5).contains(3) // true
(0...5).forEach { number in print(number) }
By default this is supported for numeric types. But can this be enabled for enums and structs as well? For example, in one application I have an enum type to represent musical notes and intervals:
public enum NoteLetter {
case C, D, E, F, G, A, B
}
public struct Interval {
var semitones : Int
public static let unison = Interval(semitones: 0)
public static let minorSecond = Interval(semitones: 1), majorSecond = Interval(semitones: 2)
public static let minorThird = Interval(semitones: 3), majorThird = Interval(semitones: 4)
public static let fourth = Interval(semitones: 5)
public static let tritone = Interval(semitones: 6)
public static let fifth = Interval(semitones: 7)
public static let minorSixth = Interval(semitones: 8), majorSixth = Interval(semitones: 9)
public static let minorSeventh = Interval(semitones: 10), majorSeventh = Interval(semitones: 11)
public static let octave = Interval(semitones: 12)
}
It would be nice to be able to define ranges based on this type like this:
NoteLetter.C..<NoteLetter.F
Interval.fifth..<Interval.octave
By default, this will give an error:
- Referencing operator function '..<' on 'Comparable' requires that 'NoteLetter' conform to 'Comparable'
- Referencing operator function '..<' on 'Comparable' requires that 'Interval' conform to 'Comparable'
To be used as a range, the type needs to implement the Strideable protocol that defines how to measure the distance between two values and how to go from one value to antother one by distance:
public enum NoteLetter: Int, Strideable {
case C, D, E, F, G, A, B
public func distance(to other: NoteLetter) -> NoteLetter.Stride {
return Stride(other.rawValue) - Stride(self.rawValue)
}
public func advanced(by n: NoteLetter.Stride) -> NoteLetter {
return NoteLetter(rawValue: numericCast(Stride(self.rawValue) + n))!
}
public typealias Stride = Int
}
The same is possible for structs:
public struct Interval : Strideable {
var semitones : Int
public static let unison = Interval(semitones: 0)
public static let minorSecond = Interval(semitones: 1), majorSecond = Interval(semitones: 2)
public static let minorThird = Interval(semitones: 3), majorThird = Interval(semitones: 4)
public static let fourth = Interval(semitones: 5)
public static let tritone = Interval(semitones: 6)
public static let fifth = Interval(semitones: 7)
public static let minorSixth = Interval(semitones: 8), majorSixth = Interval(semitones: 9)
public static let minorSeventh = Interval(semitones: 10), majorSeventh = Interval(semitones: 11)
public static let octave = Interval(semitones: 12)
public func distance(to other: Interval) -> Interval.Stride {
return Stride(other.semitones) - Stride(self.semitones)
}
public func advanced(by n: Interval.Stride) -> Interval {
return Interval(semitones: numericCast(Stride(self.semitones) + n))
}
public typealias Stride = Int
}