なにこれ?
あんまり知られてないcaseの使い方
基本
case
といえばswitch
!
switch
の簡単な例を見てみましょう。
func isA(_ c: Charactor) -> Bool {
switch c {
case "a", "A": true
default: false
}
}
普通ですね!
これは変数c
に入れられた文字と文字"a"
もしくは文字"A"
が同じであればtrue
になります。
準備
case
といえばenum
!
ということで以降で使用するenum
を定義します。
indirect enum Box {
case value(Int)
case box(Box)
case empty
}
enum
の要素宣言のcase
は今回の話とは無関係です。
if-case
switch-case
に次いでよく使われるのがif-case
です。
switch-case
のcase
とは違い後ろに = 変数
が続きます。
func isEmpty(_ box: Box) -> Bool {
if case .empty = box {
return true
}
return false
}
guard-case
あまり見たことがないですがif-case
と同様にgurad-case
も可能です。
func isValue(_ box: Box) -> Bool {
guard case .value = box else {
return false
}
return true
}
for-case
ごくまれにみられますが、for-case
も可能です。
switch-case
同様後ろには何も続きません。
Sequence
の要素のうち条件に合致するときのみコードブロックが実行されます。
func printValues(_ boxes: [Box]) {
for case .value(let i) in boxes {
print(i)
}
}
case
では.value
の値にある部分にlet i
と記述することで、Associated Valueを変数i
に代入することができます。
これはcase
が使える場面では常に有効です。
while-case
僕も初めて見ました。while-case
もいけます。
func deepSearch(_ box: Box) -> Int? {
var box = box
while case .box(let inner) = box {
box = inner
}
guard case .value(let value) = box else {
return nil
}
return value
}
let box = Box.box(Box.box(Box.value(1)))
print(deepSearch(box))
/// prints Optional(1)
repeat-case ?
出来ません。
Swiftの制御フローで唯一repeat
だけは条件にcase
が使えません。
残念。
またcase
は式ではないため式が使える場所どこでも使えるわけではありません。
例えばなんか使えそうな気がする三項演算子でも使用不能です。
Equatableとcase
当たり前ではありますがEquatable
な型はすべてcase
で使えます。
let v: Int = 0
switch v {
case 0: print("0")
case 1: print("1")
default: print("other")
}
範囲指定などはどうなっているのか
範囲を指定しているcase
はどのようになっているのか
let v: Int = 0
switch v {
case ..<0: print("Minus")
case 0: print("0")
default: print("Plus")
}
これは~=
という関数(オペレータ)を実装することで実現できます。
上の場合は
extension RangeExpression {
public static func ~= (pattern: Self, value: Bound) -> Bool {
pattern.contains(value)
}
}
が実装されているため機能します。
第一引数(左辺)がパターン、第二引数(右辺)が指定される値になります。
この関数(オペレータ)はパターンマッチ演算子と呼ばれています。
実はcase
はすべてパターンマッチ演算子~=
で検査が行われています。
先に書いたEquatable
も==
を使っているのではなく、
public func ~= <T: Equatable>(a: T, b: T) -> Bool {
return a == b
}
が標準ライブラリに入っていることでcase
に使えるようになっています。
カスタムタイプとパターンマッチ
ここまでに書いた通り、~=
を書けばいろいろなパターンマッチが可能となります。
例えば正規表現でパターンマッチなども可能になります。
struct RE {
private var re: Regex<AnyRegexOutput>
init(_ reg: String) {
do {
re = try Regex<AnyRegexOutput>(reg)
}
catch {
re = try! Regex<AnyRegexOutput>("")
}
}
static func ~= (_ lhs: Self, _ rhs: String) -> Bool {
(try? lhs.re.firstMatch(in: rhs)) != nil
}
}
を導入すれば、
func hoge(_ string: String) -> String {
switch string {
case RE("a"): "a"
case RE("ho*ge"): "ho*ge"
default: "other"
}
}
print(hoge("abc"))
/// prints a
print(hoge("hooooooooooooooooooge"))
/// prints ho*ge
print(hoge("piyo"))
/// prints other
のようにswitch-case
で正規表現が利用できるようになります。
(ここで提示したRE
は最低限動くようにしただけのものです。実際に利用するのはやめましょう。)
enumのAssociated Valueとパターンマッチ
Box.value
が特定の範囲の値の時などに何かしたいというときに使うのがこちらです。
func isValuePlus(_ box: Box) -> Bool {
switch box {
case .value(1...): true
default: false
}
}
見た目としてはかなり不思議です。
これは少しややこしくて、box
が.value
であればカッコ内の値とAssociated Valueをパターンマッチ演算子で検査する、という動作になります。
これは以下と同じです。
func isValuePlus(_ box: Box) -> Bool {
switch box {
case .value(let value) where 1... ~= value: true
default: false
}
}
// あるいは
func isValuePlus(_ box: Box) -> Bool {
if case .value(let value), case 1... = value {
return true
}
return false
}
オプショナルとパターンマッチ
オプショナルを検査するcase
では糖衣構文を利用することができます。
func isZero(_ i: Int?) -> Bool {
if case .some(0) = i {
return true
}
return false
}
これを糖衣構文を利用して書き換えるとこうなります。
func isZero(_ i: Int?) -> Bool {
if case 0? = i {
return true
}
return false
}
次に、あるOptional<Int>
な値を3つの場合、nil
, nil
でなく0
未満, nil
でなく0
に分けることを考えます。
これも先ほどの「enumのAssociated Valueとパターンマッチ」と同じです。
func sign(_ value: Int?) -> String {
switch value {
case .some(..<0): "Minus"
case .some(0...): "0 or Plus"
default: "Nil"
}
}
これは以下のように書くこともできます。
func sign(_ value: Int?) -> String {
switch value {
case (..<0)?: "Minus"
case (0...)?: "0 or Plus"
default: "Nil"
}
}
タプルとcase
タプルをswitch-case
で場合分けすることがありますが、こちらにも少し変わった方法があるので見てみましょう。
みんな大好きFizzBuzz
です。
func fizzBuzz1(_ i: Int) -> String {
guard i > 0 else { fatalError() }
switch (i.isMultiple(of: 3), i.isMultiple(of: 5)) {
case (true, true): return "FizzBuzz"
case (true, _): return "Fizz"
case (_, true): return "Buzz"
default: return "\(i)"
}
}
こちらの2番目3番目のcase
に含まれる_
はマッチしてもしなくてもよい、つまりどんな値でもtrue
になるパターンです。
まとめ
Swiftのcase
はとても強力です。
うまく使うことで複雑なコードが簡素になることもあります。
条件分岐を考えるときはcase
のことを少しだけ思い出してあげてください。