概要
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-head
とdot-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))
と同じことになりました!
同じように、OpenRange
もPartialRangeGreaterThan
も実装することができます。
ここで定義した演算子の問題点
前述のように<
はあくまでpostfix operatorなので、0 <.. 10
のように0
と<
のあいだにスペースを含めることができません(一方で0< .. 10
はOK)。Swiftの言語仕様上、これが限界です。
もうちっとだけ続くんじゃ?
これだけ“範囲を表す構造体”が増えると、AnyRange<Bound>
みたいなtype erasureを実装したくなりますよね。実際にSwiftRangesでは実装してあります。が、その話はまた別の機会にでも…。
→ 続き: 「Rangeの種類が足りない!② 〜初めてのtype erasure〜」