なにこれ?
前回[Swift] caseの使い方を覚えて一段上のプログラミングをの続きにあたります。
case
をもうちょっと深く掘り下げてみます。
以下ではThe Swift Programming LanguageのSummary of the Grammarを引用していきます。
この部分が引用です。
BNF記法っぽいやつ
Summary of the Grammarを見ていきます。
まずfor
は以下のようになっています。
for-in-statement → for case? pattern in expression where-clause? code-block
for-case
の後ろに続くのはpattern
であることが分かりました。
次にwhile
、if
およびguard
を見てみましょう。
while-statement → while condition-list code-block
if-statement → if condition-list code-block else-clause?
guard-statement → guard condition-list else code-block
これらは全てcase
の後ろはcondition-list
となっています。
condition-list
は以下の通りです。
condition-list → condition | condition , condition-list
condition → expression | availability-condition | case-condition | optional-binding-condition
case-condition → case pattern initializer
こちらもcase
に続くものはpattern
です。
最後にswitch
です。
switch-statement → switch expression { switch-cases? }
switch-cases → switch-case switch-cases?
switch-case → case-label statements
switch-case → default-label statements
switch-case → conditional-switch-case
case-label → attributes? case case-item-list :
case-item-list → pattern where-clause? | pattern where-clause? , case-item-list
ちょっと込み入っていますが、他と同様case
の後ろに続くものはpattern
です。
pattern
case
の後ろに続くものは常にpattern
である事が分かりました。
ではpattern
にはどのようなものはあるのかを見ていきましょう。
pattern → wildcard-pattern type-annotation?
pattern → identifier-pattern type-annotation?
pattern → value-binding-pattern
pattern → tuple-pattern type-annotation?
pattern → enum-case-pattern
pattern → optional-pattern
pattern → type-casting-pattern
pattern → expression-pattern
結構ありますね。
順番に詳しく見ていきます。
wildcard-pattern
pattern → wildcard-pattern type-annotation?
wildcard-pattern → _
前回ちょこっと出てきた何にでもマッチする _ です。
let value = 10
switch value {
case 0: print("0")
case 1: print("1")
case _: print("other")
}
type-annotation
が使えますが、あまり使いどころはないと思われます。
identifier-pattern
pattern → identifier-pattern type-annotation?
identifier-pattern → identifier
identifier
つまり変数名や関数名タイプ名などがこれに当たります。
let range = 0...10
let i = 3
if case range = i {
print("\(i) is in 0 to 10")
}
などです。
value-binding-pattern
pattern → value-binding-pattern
value-binding-pattern → var pattern | let pattern
これはパターンにマッチした値を取得するものです。
let value = 10
switch value {
case let v: print("value is \(v)")
}
またlet
/var
の後にはpattern
が続きますが、どこかに値を格納するためのidentifier-pattern
を含む必要があります。
tuple-pattern
pattern → tuple-pattern type-annotation?
tuple-pattern → ( tuple-pattern-element-list? )
tuple-pattern-element-list → tuple-pattern-element | tuple-pattern-element , tuple-pattern-element-list
tuple-pattern-element → pattern | identifier : pattern
これはそのまま、タプルを検査するものです。
let tuple = (0, "a")
switch tuple {
case (0, "a"): print("left is 0 and right is a")
case (0, _): print("left is zero")
case (_, "a"): print("right is a")
default: print("other")
}
() の中身がpattern
であることに注意してください。
optional-pattern
pattern → optional-pattern
optional-pattern → identifier-pattern ?
let optionalValue: Int? = 0
if case let wrappedValue? = a {
print("\(wrappedValue)")
}
value-binding-pattern
のlet
の後に続くpattern
がoptional-pattern
になっています。
type-casting-pattern
pattern → type-casting-pattern
type-casting-pattern → is-pattern | as-pattern
is-pattern → is type
as-pattern → pattern as type
特定の型の場合にマッチするパターンです。
let value: Any = 0
switch value {
case is Int: print("Int")
case let s as String: print("String \(s)")
default: print("other")
}
expression-pattern
pattern → expression-pattern
expression-pattern → expression
いっぱいありすぎなので省略
パターンマッチ演算子( ~= )が利用可能なすべての式がこれに当てはまります。
struct S {
var range: ClosedRange<Int> = 0...1
static func ~= (l: Self, r: Int) -> Bool { l.range ~= r }
func newRange() -> ClosedRange<Int> { 0..1 }
}
let value = 10
switch value {
case ..<0: ()
case 0: ()
case S(): ()
case S().range: ()
case S().newRange(): ()
default: ()
}
ここからが本題です
enum-case-pattern
このパターンはあえて後ろに回していました。
pattern → enum-case-pattern
enum-case-pattern → type-identifier? . enum-case-name tuple-pattern?
type-identifier
はenumの型名です。省略可能です。
enum-case-name
はenumの要素名です。
そして、そのあとに続くのがtuple-pattern
です。
case
においてenumの要素名の後ろに続く()
はAssociated Valueの()
ではなくtuple-pattern
の()
です。
tuple-pattern
のGrammarを改めてみてみましょう。
pattern → tuple-pattern type-annotation?
tuple-pattern → ( tuple-pattern-element-list? )
tuple-pattern-element-list → tuple-pattern-element | tuple-pattern-element , tuple-pattern-element-list
tuple-pattern-element → pattern | identifier : pattern
taple-pattern
のタプルの要素部分に入るものはpattern
です。
pattern
であればなんでも入れてよいのです。
enum-case-pattern
を含めて今まで出てきたすべてのパターンが対象です。
まず普通のパターンです。
enum E { case a, b, c }
let e = E.a
switch e {
case .a: ()
case .b: ()
case .c: ()
}
つづいてAssociated Valueを持つenumのパターンです。
enum E {
case value1(Int)
case value2(Int, String)
}
let e = E.value1(10)
switch e {
case .value1(0): print("value1 0")
case .value1(..<0): print("value1 minus")
case .value1(let i): print("value1 \(i)")
case .value2(0, _): print("value2 0")
case .value2(_, let string): print("value2 \(string)")
case .value2(0...10, "str"): print("value2 0 to 10 and str");
default: ()
}
enumの要素名の後ろの()
はtaple-pattern
の()
ですので結構なんでも入ります。
ではさらに
enum AnyBox {
case value(Any)
}
let e = AnyBox.value(0)
switch e {
case .value(is Int): print("value is Int")
case .value(let str as String): print("value is String(\(str))")
default: ()
}
type-casting-pattern
ももちろん大丈夫です。
さらには
indirect Box {
case .value(Int)
case .box(Box)
}
let e = Box.box(.value(8))
switch e {
case .box(.value(0...10)): print("0 to 10 in box")
default: ()
}
enum-case-pattern
の入れ子も大丈夫です。
enum-case-pattern
は大抵の無茶は通ってしまいます。
まとめ
やはり、Swiftのcase
とenum
の組み合わせは極めて強力といえるでしょう。
case
を上手に使いこなして一歩進んだプログラムを書いていきましょう。