こんにちは、iOS開発チームの本柳です。
Pythonなどでは値の範囲チェック(m < x and n > x
のような評価)をする時、数学の評価式のようなm < x < n
と記述することが出来、大変分かりやすいですよね。
swiftでは、動的に変化する画面の座標が範囲内にあるか?という評価を行うケースがそれなりに多く、このような評価式をつくることが出来ると大変に便利。
便利ならば作ってしまおう!ということで~
を使って似たような評価式を書けるようにしてみました。
オペレーターの定義
まずは、<
,<=
,>
,>=
に該当するオペレーターを定義します。
Pythonだとm > x > n
のようにも書けますが、これはn < x < m
と等価であるので、<
,<=
だけを書ければ良いはずです。
なので、<
を~!
、<=
を~
に置き換えて定義出来るようにします。
// `<=`に該当するオペレーター infix operator ~ { associativity left } // `<`に該当するオペレーター infix operator ~! { associativity left }
こうして定義することで、Swiftではm ~ x ~ n
のように記述することが可能になります。
これはm <= x <= n
と等価になります。
Swiftでは常に左辺の結果を利用したいので、オペレーターにはassociativity left
を指定して左結合するようにしておきます。
オペレーターが利用する処理の実装
実装方針は下記の通りです。
- 最初の
~
オペレーターは中間比較オブジェクトを生成する - 次の
~
オペレーターは左辺に比較オブジェクトを取り、右辺にComparableな値をとって真偽値を返す
比較オブジェクト(_Range
として定義することにします)は中間状態、完全状態を持ちますが、この状態はファントムタイプで実装することにします。
まずはファントムタイプの定義です。
class _RangeState {} class _RangeIntermediate: _RangeState {} class _RangePerfection: _RangeState {}
次に_Range
オブジェクトの定義を行います。
デフォルトの_Range
オブジェクトはコンストラクタをプライベートにして、さらに、中間状態をもつインスタンスのファクトリメソッドを定義しておくことで、初回生成は常に中間状態を持つように定義します。
enum _Range_MinOp { case less case lessOrEq } enum _Range_MaxOp { case gt case gtOrEq } struct _Range<T: Comparable, U: _RangeState> { typealias MinOp = _Range_MinOp typealias MaxOp = _Range_MaxOp private let value: T private let min: T private let minOp: MinOp private let max: T? private let maxOp: MaxOp? // コンストラクタはプライベートにして、状態を持たないインスタンスを生成出来ないようにします private init(value: T, min: T, minOp: MinOp, max: T?, maxOp: MaxOp?) { self.value = value self.min = min self.minOp = minOp self.max = max self.maxOp = maxOp } // 初期状態では中間オブジェクトのみ生成することが出来ます static func buildIntermediate(value: T, min: T, minOp: MinOp) -> _Range<T, _RangeIntermediate> { return _Range<T, _RangeIntermediate>(value: value, min: min, minOp: minOp, max: nil, maxOp: nil) } }
デフォルトの_Range
オブジェクトの定義が出来たら、今度は中間状態の_Range
オブジェクトを拡張して、完全状態の_Range
オブジェクトを生成出来るようにします。
extension _Range where U: _RangeIntermediate { func toPerfection(max max: T, maxOp: MaxOp) -> _Range<T, _RangePerfection> { return _Range<T, _RangePerfection>(value: value, min: min, minOp: minOp, max: max, maxOp: maxOp) } }
最後に完全状態の_Range
インスタンスに値の評価する処理を実装すれば_Range
オブジェクトは完成です。
extension _Range where U: _RangePerfection { var valueInRange: Bool { return valueLessThanMinimum && valueGtThanMaximum } private var valueLessThanMinimum: Bool { switch minOp { case .less: return value > min case .lessOrEq: return value >= min } } private var valueGtThanMaximum: Bool { switch maxOp! { case .gt: return value < max case .gtOrEq: return value <= max } } }
オペレーターの関数を定義
ここまで出来たら最後に、演算子に適合させたい型とオペレーターを紐付ける処理を書いて完了です。
func ~<T: Comparable>(lhs: T, rhs: T) -> _Range<T, _RangeIntermediate> { return _Range<T, _RangeState>.buildIntermediate(rhs, min: lhs, minOp: .lessOrEq) } func ~!<T: Comparable>(lhs: T, rhs: T) -> _Range<T, _RangeIntermediate> { return _Range<T, _RangeState>.buildIntermediate(rhs, min: lhs, minOp: .less) } func ~<T: Comparable>(lhs: _Range<T, _RangeIntermediate>, rhs: T) -> Bool { let range = lhs.toPerfection(max: rhs, maxOp: .gtOrEq) return range.valueInRange } func ~!<T: Comparable>(lhs: _Range<T, _RangeIntermediate>, rhs: T) -> Bool { let range = lhs.toPerfection(max: rhs, maxOp: .gt) return range.valueInRange }
これで下記のようにシンプルに範囲を比較する処理が書けるようになりました。
let value1 = 10 print(1 ~ value1 ~ 10) // -> true print(1 ~ value1 ~! 10) // -> false let value2 = 1 print(1 ~ value2 ~ 10) // -> true print(1 ~! value2 ~ 10) // -> false
株式会社ガラパゴスでは、Swiftをつかってプログラミングを楽しみたいエンジニアを絶賛大募集中です!
RECRUIT | 株式会社ガラパゴス iPhone/iPad/Androidのスマートフォンアプリ開発
たくさんのご応募、お待ちしております。