Edited at

[Swift] Rangeの種類が足りない!① 〜演算子との戦い〜

More than 1 year has passed since last update.


概要

Swiftにおいて標準で実装されていない“範囲を表す構造体”を実装してみたよ!

SwiftRangesという名前で公開したよ!

演算子と戦ったよ!


おさらい

Swiftには標準で“範囲を表す構造体”が5種類あります(Countable*は除く1; また、UnboundedRange別の記事で説明した通り構造体ではありません)。

範囲を表す構造体は次の通り:



  • ClosedRange: lower...upper


  • Range: lower..<upper


  • PartialRangeFrom: lower...


  • PartialRangeThrough: ...upper


  • PartialRangeUpTo: ..<upper

何が足りない?

…そう、「lowerを含まない範囲」というのが実装されていないのです。必要ないからかな?


足りないものは補おう

というわけで、「lowerを含まない範囲」を実装していきましょう。



  • LeftOpenRange: lowerは含まないけどupperは含む範囲


  • OpenRange: lowerもupperも含まない範囲


  • PartialRangeGreaterThan: lowerを含まず、上限がない範囲

これら3つの構造体を定義するのはとても簡単です。次のようなコードとなりました:

public struct LeftOpenRange<Bound: Comparable> {

public let lowerBound: Bound
public let upperBound: Bound
}

public struct OpenRange<Bound: Comparable> {
public let lowerBound: Bound
public let upperBound: Bound
internal let _isEmpty: Bool
}

public struct PartialRangeGreaterThan<Bound: Comparable> {
public let lowerBound: Bound
}

OpenRangeだけ_isEmptyとかいうプロパティがあるけど、この記事では気にしない気にしない。


演算子を定義しよう 〜実は一筋縄ではいかない〜

たとえば、0以上10未満を表すRange<Int>は、..<というinfix operatorを用いて0..<10というように書くことができます。となると、たとえば0超10以下を表すLeftOpenRange<Int>0<..10と書きたくなりませんか?私はなります。

では、さっそく、<..というinfix operatorを定義しましょう。

infix operator <.. :RangeFormationPrecedence

と書けば…

error: r.swift:1:17: error: consecutive statements on a line must be separated by ';'

infix operator <.. :RangeFormationPrecedence
^
;

error: r.swift:1:17: error: operator with postfix spacing cannot start a subexpression
infix operator <.. :RangeFormationPrecedence
^

error: r.swift:1:20: error: expected expression
infix operator <.. :RangeFormationPrecedence

…できません!

なぜ、できないのか?


定義できる演算子とは?

SwiftのLanguage Referenceを見てみましょう。Grammar of operatorsを引用します:


operator → operator-head operator-characters(opt)

operator → dot-operator-head dot-operator-characters

operator-head → / | = | - | + | ! | * | % | < | > | & | | | ^ | ~ | ?

operator-head → U+00A1–U+00A7

operator-head → U+00A9 or U+00AB

operator-head → U+00AC or U+00AE

operator-head → U+00B0–U+00B1, U+00B6, U+00BB, U+00BF, U+00D7, or U+00F7

operator-head → U+2016–U+2017 or U+2020–U+2027

operator-head → U+2030–U+203E

operator-head → U+2041–U+2053

operator-head → U+2055–U+205E

operator-head → U+2190–U+23FF

operator-head → U+2500–U+2775

operator-head → U+2794–U+2BFF

operator-head → U+2E00–U+2E7F

operator-head → U+3001–U+3003

operator-head → U+3008–U+3030

operator-character → operator-head

operator-character → U+0300–U+036F

operator-character → U+1DC0–U+1DFF

operator-character → U+20D0–U+20FF

operator-character → U+FE00–U+FE0F

operator-character → U+FE20–U+FE2F

operator-character → U+E0100–U+E01EF

operator-characters → operator-character operator-characters(opt)

dot-operator-head → .

dot-operator-character → . | operator-character

dot-operator-characters → dot-operator-character dot-operator-characters(opt)

binary-operator → operator

prefix-operator → operator

postfix-operator → operator


まず、定義できるoperatorは2種類あって、"operator-head operator-characters(opt)"と"dot-operator-head dot-operator-characters"です。よく見ると、operator-character(s).は含まれておらず、dot-operator-headdot-operator-chracter(s)にのみ.は含まれています。

簡単に言うと、.で始まる演算子以外では2文字目以降に.を使えない」のです2

結局、<..と書くと、<..という2つの演算子が並んでいると解釈されてしまいます。だから、上記のようなエラーになってしまったのです。


2つの演算子を定義すればいいんだ

Swiftでは0<..10と書くと(0<)..(10)というように解釈され得ることが分かりました。ということは、<というpostfix operatorと..というinfix operatorを定義すればうまくいくかもしれません。

まずはExcludedLowerBoundという構造体を使って前者を実装することにします:

public struct ExcludedLowerBound<Bound> where Bound: Comparable {

public let lowerBound:Bound
public init(_ lowerBound:Bound) { self.lowerBound = lowerBound }
}

postfix operator <
public postfix func < <T>(_ lowerBound:T) -> ExcludedLowerBound<T> where T: Comparable {
return ExcludedLowerBound<T>(lowerBound)
}

こうすることで、0<と書くとExcludedLowerBound<Int>(0)と書くのと同じことになります。

次は、..の方の実装です:

extension LeftOpenRange {

public init(uncheckedBounds bounds: (lower: Bound, upper: Bound)) {
self.lowerBound = bounds.lower
self.upperBound = bounds.upper
}
}

infix operator ..: RangeFormationPrecedence
public func .. <T>(lhs:ExcludedLowerBound<T>, upper:T) -> LeftOpenRange<T> {
let lower = lhs.lowerBound
guard lower <= upper else {
fatalError("Can't form Range with upperBound < lowerBound")
}
return LeftOpenRange(uncheckedBounds:(lower:lower, upper:upper))
}

左辺にExcludedLowerBound<T>型、右辺にT型をとる、infix operatorとして定義しました。

これで、0<..10(0<)..(10)ExcludedLowerBound<Int>(0) .. (10)LeftOpenRange<Int>(uncheckedBounds:(lower:0, upper:10))と同じことになりました!

同じように、OpenRangePartialRangeGreaterThanも実装することができます。


ここで定義した演算子の問題点

前述のように<はあくまでpostfix operatorなので、0 <.. 10のように0<のあいだにスペースを含めることができません(一方で0< .. 10はOK)。Swiftの言語仕様上、これが限界です。


もうちっとだけ続くんじゃ?

これだけ“範囲を表す構造体”が増えると、AnyRange<Bound>みたいなtype erasureを実装したくなりますよね。実際にSwiftRangesでは実装してあります。が、その話はまた別の機会にでも…。

→ 続き: 「Rangeの種類が足りない!② 〜初めてのtype erasure〜






  1. だってCountable*はSwift 4.2からConditional Conformance的なtypealiasになるからね→PR#13342 



  2. dot-operator-headは"."のみなので。