※バージョンが間違えてたので修正・・・すいません
Equatable
がSwift4.1から使いやすくなりました 🎉🎉🎉🎉
みなさんご存知の通り、Swift4.1からEquatable
が使いやすくなりました。
Swift4までは、Equatable
プロトコルに準拠するときは、以下のようにstatic public func ==(lhs:rhs:) -> Bool
を自分で書き上げないといけませんでしたね。
enum Nyan: Equatable {
case hoge
case fuga
static public func ==(lhs: Nyan, rhs: Nyan) -> Bool {
switch (lhs, rhs) {
case (.hoge, .hoge), (.fuga, .fuga):
return true
default:
return false
}
}
}
もしメンバ変数やcase
の数が膨大になれば、switch
文中の比較はその分必要になりますので、当然コードの量も膨大になります。
値付きのcase
(e.g. case hoge(Int)
)になると、中の値まで比較しないといけなくなりますね・・・。
でも大丈夫!Swift4.1から、static public func == (lhs:rhs:) -> Bool
を自分で書かなくて良くなりました。
わーい!コード量が減るよ!劇的BeforeAfterだね!
enum Nyan: Equatable { // Equatableに準拠しますよ〜ということだけを言えばいい
case hoge
case fuga
}
・・・なんで書かないでよくなったの?
さて、このときですがNyan
では何が起こっているのでしょうか?
今まで書くべきだったstatic public func ==(lhs:rhs:) -> Bool
は果たしてどこに?!
では、以下のコードをつかって、Swift4.1からの新しいEquatable
を覗いてみましょう!
enum Nyan: Equatable {
case hoge
case fuga
}
enum
によるEquatable
を、-emit-silgen
で覗いてみる
こういうときはSILを見るのがいいかもしれません。
では、enum
によるEquatable
を、早速-emit-silgen
でraw SILを生成して覗いてみましょう!
swiftc -emit-silgen nyan.swift
で、出力されたraw SILがこちらです!(この時点でraw SILは読み流して結構です)
sil_stage raw
import Builtin
import Swift
import SwiftShims
enum Nyan : Equatable {
case hoge
case fuga
var hashValue: Int { get }
func hash(into hasher: inout Hasher)
@_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: Nyan, _ b: Nyan) -> Bool
}
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
%2 = integer_literal $Builtin.Int32, 0 // user: %3
%3 = struct $Int32 (%2 : $Builtin.Int32) // user: %4
return %3 : $Int32 // id: %4
} // end sil function 'main'
// Nyan.hashValue.getter
sil hidden @$S1A4NyanO9hashValueSivg : $@convention(method) (Nyan) -> Int {
// %0 // users: %3, %1
bb0(%0 : $Nyan):
debug_value %0 : $Nyan, let, name "self", argno 1 // id: %1
%2 = alloc_stack $Nyan // users: %6, %5, %3
store %0 to [trivial] %2 : $*Nyan // id: %3
// function_ref _hashValue<A>(for:)
%4 = function_ref @$Ss10_hashValue3forSix_tSHRzlF : $@convention(thin) <τ_0_0 where τ_0_0 : Hashable> (@in_guaranteed τ_0_0) -> Int // user: %5
%5 = apply %4<Nyan>(%2) : $@convention(thin) <τ_0_0 where τ_0_0 : Hashable> (@in_guaranteed τ_0_0) -> Int // user: %7
dealloc_stack %2 : $*Nyan // id: %6
return %5 : $Int // id: %7
} // end sil function '$S1A4NyanO9hashValueSivg'
// _hashValue<A>(for:)
sil [serialized] [always_inline] @$Ss10_hashValue3forSix_tSHRzlF : $@convention(thin) <τ_0_0 where τ_0_0 : Hashable> (@in_guaranteed τ_0_0) -> Int
// Nyan.hash(into:)
sil hidden @$S1A4NyanO4hash4intoys6HasherVz_tF : $@convention(method) (@inout Hasher, Nyan) -> () {
// %0 // users: %29, %2
// %1 // users: %7, %3
bb0(%0 : $*Hasher, %1 : $Nyan):
debug_value_addr %0 : $*Hasher, var, name "hasher", argno 1 // id: %2
debug_value %1 : $Nyan, let, name "self", argno 2 // id: %3
%4 = alloc_box ${ var Int }, var, name "discriminator" // user: %5
%5 = mark_uninitialized [var] %4 : ${ var Int } // users: %34, %6
%6 = project_box %5 : ${ var Int }, 0 // users: %24, %20, %12
switch_enum %1 : $Nyan, case #Nyan.hoge!enumelt: bb1, case #Nyan.fuga!enumelt: bb2 // id: %7
bb1: // Preds: bb0
%8 = metatype $@thin Int.Type // user: %11
%9 = integer_literal $Builtin.Int2048, 0 // user: %11
// function_ref Int.init(_builtinIntegerLiteral:)
%10 = function_ref @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %11
%11 = apply %10(%9, %8) : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %13
%12 = begin_access [modify] [unknown] %6 : $*Int // users: %14, %13
assign %11 to %12 : $*Int // id: %13
end_access %12 : $*Int // id: %14
br bb3 // id: %15
bb2: // Preds: bb0
%16 = metatype $@thin Int.Type // user: %19
%17 = integer_literal $Builtin.Int2048, 1 // user: %19
// function_ref Int.init(_builtinIntegerLiteral:)
%18 = function_ref @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %19
%19 = apply %18(%17, %16) : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %21
%20 = begin_access [modify] [unknown] %6 : $*Int // users: %22, %21
assign %19 to %20 : $*Int // id: %21
end_access %20 : $*Int // id: %22
br bb3 // id: %23
bb3: // Preds: bb2 bb1
%24 = begin_access [read] [unknown] %6 : $*Int // users: %26, %25
%25 = load [trivial] %24 : $*Int // user: %28
end_access %24 : $*Int // id: %26
%27 = alloc_stack $Int // users: %33, %31, %28
store %25 to [trivial] %27 : $*Int // id: %28
%29 = begin_access [modify] [unknown] %0 : $*Hasher // users: %32, %31
// function_ref Hasher.combine<A>(_:)
%30 = function_ref @$Ss6HasherV7combineyyxSHRzlF : $@convention(method) <τ_0_0 where τ_0_0 : Hashable> (@in_guaranteed τ_0_0, @inout Hasher) -> () // user: %31
%31 = apply %30<Int>(%27, %29) : $@convention(method) <τ_0_0 where τ_0_0 : Hashable> (@in_guaranteed τ_0_0, @inout Hasher) -> ()
end_access %29 : $*Hasher // id: %32
dealloc_stack %27 : $*Int // id: %33
destroy_value %5 : ${ var Int } // id: %34
%35 = tuple () // user: %36
return %35 : $() // id: %36
} // end sil function '$S1A4NyanO4hash4intoys6HasherVz_tF'
// Int.init(_builtinIntegerLiteral:)
sil [transparent] [serialized] @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int
// Hasher.combine<A>(_:)
sil [serialized] [always_inline] @$Ss6HasherV7combineyyxSHRzlF : $@convention(method) <τ_0_0 where τ_0_0 : Hashable> (@in_guaranteed τ_0_0, @inout Hasher) -> ()
// static Nyan.__derived_enum_equals(_:_:)
sil hidden @$S1A4NyanO21__derived_enum_equalsySbAC_ACtFZ : $@convention(method) (Nyan, Nyan, @thin Nyan.Type) -> Bool {
// %0 // users: %9, %3
// %1 // users: %29, %4
// %2 // user: %5
bb0(%0 : $Nyan, %1 : $Nyan, %2 : $@thin Nyan.Type):
debug_value %0 : $Nyan, let, name "a", argno 1 // id: %3
debug_value %1 : $Nyan, let, name "b", argno 2 // id: %4
debug_value %2 : $@thin Nyan.Type, let, name "self", argno 3 // id: %5
%6 = alloc_box ${ var Int }, var, name "index_a" // user: %7
%7 = mark_uninitialized [var] %6 : ${ var Int } // users: %56, %8
%8 = project_box %7 : ${ var Int }, 0 // users: %47, %22, %14
switch_enum %0 : $Nyan, case #Nyan.hoge!enumelt: bb1, case #Nyan.fuga!enumelt: bb2 // id: %9
bb1: // Preds: bb0
%10 = metatype $@thin Int.Type // user: %13
%11 = integer_literal $Builtin.Int2048, 0 // user: %13
// function_ref Int.init(_builtinIntegerLiteral:)
%12 = function_ref @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %13
%13 = apply %12(%11, %10) : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %15
%14 = begin_access [modify] [unknown] %8 : $*Int // users: %16, %15
assign %13 to %14 : $*Int // id: %15
end_access %14 : $*Int // id: %16
br bb3 // id: %17
bb2: // Preds: bb0
%18 = metatype $@thin Int.Type // user: %21
%19 = integer_literal $Builtin.Int2048, 1 // user: %21
// function_ref Int.init(_builtinIntegerLiteral:)
%20 = function_ref @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %21
%21 = apply %20(%19, %18) : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %23
%22 = begin_access [modify] [unknown] %8 : $*Int // users: %24, %23
assign %21 to %22 : $*Int // id: %23
end_access %22 : $*Int // id: %24
br bb3 // id: %25
bb3: // Preds: bb2 bb1
%26 = alloc_box ${ var Int }, var, name "index_b" // user: %27
%27 = mark_uninitialized [var] %26 : ${ var Int } // users: %55, %28
%28 = project_box %27 : ${ var Int }, 0 // users: %50, %42, %34
switch_enum %1 : $Nyan, case #Nyan.hoge!enumelt: bb4, case #Nyan.fuga!enumelt: bb5 // id: %29
bb4: // Preds: bb3
%30 = metatype $@thin Int.Type // user: %33
%31 = integer_literal $Builtin.Int2048, 0 // user: %33
// function_ref Int.init(_builtinIntegerLiteral:)
%32 = function_ref @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %33
%33 = apply %32(%31, %30) : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %35
%34 = begin_access [modify] [unknown] %28 : $*Int // users: %36, %35
assign %33 to %34 : $*Int // id: %35
end_access %34 : $*Int // id: %36
br bb6 // id: %37
bb5: // Preds: bb3
%38 = metatype $@thin Int.Type // user: %41
%39 = integer_literal $Builtin.Int2048, 1 // user: %41
// function_ref Int.init(_builtinIntegerLiteral:)
%40 = function_ref @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %41
%41 = apply %40(%39, %38) : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %43
%42 = begin_access [modify] [unknown] %28 : $*Int // users: %44, %43
assign %41 to %42 : $*Int // id: %43
end_access %42 : $*Int // id: %44
br bb6 // id: %45
bb6: // Preds: bb5 bb4
%46 = metatype $@thin Int.Type // user: %54
%47 = begin_access [read] [unknown] %8 : $*Int // users: %49, %48
%48 = load [trivial] %47 : $*Int // user: %54
end_access %47 : $*Int // id: %49
%50 = begin_access [read] [unknown] %28 : $*Int // users: %52, %51
%51 = load [trivial] %50 : $*Int // user: %54
end_access %50 : $*Int // id: %52
// function_ref static Int.== infix(_:_:)
%53 = function_ref @$SSi2eeoiySbSi_SitFZ : $@convention(method) (Int, Int, @thin Int.Type) -> Bool // user: %54
%54 = apply %53(%48, %51, %46) : $@convention(method) (Int, Int, @thin Int.Type) -> Bool // user: %57
destroy_value %27 : ${ var Int } // id: %55
destroy_value %7 : ${ var Int } // id: %56
return %54 : $Bool // id: %57
} // end sil function '$S1A4NyanO21__derived_enum_equalsySbAC_ACtFZ'
// static Int.== infix(_:_:)
sil [transparent] [serialized] @$SSi2eeoiySbSi_SitFZ : $@convention(method) (Int, Int, @thin Int.Type) -> Bool
// protocol witness for Hashable.hashValue.getter in conformance Nyan
sil private [transparent] [thunk] @$S1A4NyanOSHAASH9hashValueSivgTW : $@convention(witness_method: Hashable) (@in_guaranteed Nyan) -> Int {
// %0 // user: %1
bb0(%0 : $*Nyan):
%1 = load [trivial] %0 : $*Nyan // user: %3
// function_ref Nyan.hashValue.getter
%2 = function_ref @$S1A4NyanO9hashValueSivg : $@convention(method) (Nyan) -> Int // user: %3
%3 = apply %2(%1) : $@convention(method) (Nyan) -> Int // user: %4
return %3 : $Int // id: %4
} // end sil function '$S1A4NyanOSHAASH9hashValueSivgTW'
// protocol witness for Hashable.hash(into:) in conformance Nyan
sil private [transparent] [thunk] @$S1A4NyanOSHAASH4hash4intoys6HasherVz_tFTW : $@convention(witness_method: Hashable) (@inout Hasher, @in_guaranteed Nyan) -> () {
// %0 // user: %4
// %1 // user: %2
bb0(%0 : $*Hasher, %1 : $*Nyan):
%2 = load [trivial] %1 : $*Nyan // user: %4
// function_ref Nyan.hash(into:)
%3 = function_ref @$S1A4NyanO4hash4intoys6HasherVz_tF : $@convention(method) (@inout Hasher, Nyan) -> () // user: %4
%4 = apply %3(%0, %2) : $@convention(method) (@inout Hasher, Nyan) -> ()
%5 = tuple () // user: %6
return %5 : $() // id: %6
} // end sil function '$S1A4NyanOSHAASH4hash4intoys6HasherVz_tFTW'
// protocol witness for static Equatable.== infix(_:_:) in conformance Nyan
sil private [transparent] [thunk] @$S1A4NyanOSQAASQ2eeoiySbx_xtFZTW : $@convention(witness_method: Equatable) (@in_guaranteed Nyan, @in_guaranteed Nyan, @thick Nyan.Type) -> Bool {
// %0 // user: %3
// %1 // user: %4
bb0(%0 : $*Nyan, %1 : $*Nyan, %2 : $@thick Nyan.Type):
%3 = load [trivial] %0 : $*Nyan // user: %7
%4 = load [trivial] %1 : $*Nyan // user: %7
%5 = metatype $@thin Nyan.Type // user: %7
// function_ref static Nyan.__derived_enum_equals(_:_:)
%6 = function_ref @$S1A4NyanO21__derived_enum_equalsySbAC_ACtFZ : $@convention(method) (Nyan, Nyan, @thin Nyan.Type) -> Bool // user: %7
%7 = apply %6(%3, %4, %5) : $@convention(method) (Nyan, Nyan, @thin Nyan.Type) -> Bool // user: %8
return %7 : $Bool // id: %8
} // end sil function '$S1A4NyanOSQAASQ2eeoiySbx_xtFZTW'
sil_witness_table hidden Nyan: Hashable module A {
base_protocol Equatable: Nyan: Equatable module A
method #Hashable.hashValue!getter.1: <Self where Self : Hashable> (Self) -> () -> Int : @$S1A4NyanOSHAASH9hashValueSivgTW // protocol witness for Hashable.hashValue.getter in conformance Nyan
method #Hashable.hash!1: <Self where Self : Hashable> (Self) -> (inout Hasher) -> () : @$S1A4NyanOSHAASH4hash4intoys6HasherVz_tFTW // protocol witness for Hashable.hash(into:) in conformance Nyan
}
sil_witness_table hidden Nyan: Equatable module A {
method #Equatable."=="!1: <Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool : @$S1A4NyanOSQAASQ2eeoiySbx_xtFZTW // protocol witness for static Equatable.== infix(_:_:) in conformance Nyan
}
むむ!なにやらたくさんソースがあるぞ!では、随所随所抜粋して、読んで考えていきましょう。
まず、もとのenum
っぽいソースがありますね。抜粋してみます!
enum Nyan : Equatable {
case hoge
case fuga
var hashValue: Int { get }
func hash(into hasher: inout Hasher)
@_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: Nyan, _ b: Nyan) -> Bool
}
この中に、今まで実装していたstatic func == (lhs:rhs:)
と同じと思われる@_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: Nyan, _ b: Nyan) -> Bool
がありました。
では、SILをもうちょっと覗いてみます。最後の方に、Nyan
型の引数を2つとってBoolを返す関数のように見えるコードが見えました!
// protocol witness for static Equatable.== infix(_:_:) in conformance Nyan
sil private [transparent] [thunk] @$S1A4NyanOSQAASQ2eeoiySbx_xtFZTW : $@convention(witness_method: Equatable) (@in_guaranteed Nyan, @in_guaranteed Nyan, @thick Nyan.Type) -> Bool {
// %0 // user: %3
// %1 // user: %4
bb0(%0 : $*Nyan, %1 : $*Nyan, %2 : $@thick Nyan.Type):
%3 = load [trivial] %0 : $*Nyan // user: %7
%4 = load [trivial] %1 : $*Nyan // user: %7
%5 = metatype $@thin Nyan.Type // user: %7
// function_ref static Nyan.__derived_enum_equals(_:_:)
%6 = function_ref @$S1A4NyanO21__derived_enum_equalsySbAC_ACtFZ : $@convention(method) (Nyan, Nyan, @thin Nyan.Type) -> Bool // user: %7
%7 = apply %6(%3, %4, %5) : $@convention(method) (Nyan, Nyan, @thin Nyan.Type) -> Bool // user: %8
return %7 : $Bool // id: %8
} // end sil function '$S1A4NyanOSQAASQ2eeoiySbx_xtFZTW'
// protocol witness for static Equatable.== infix(_:_:) in conformance Nyan
というコメントから、これはEquatable
の準拠によって生成された==
のコードだと考えられます。
ここで__derived_enum_equals
が
%6 = function_ref @$S1A4NyanO21__derived_enum_equalsySbAC_ACtFZ : $@convention(method) (Nyan, Nyan, @thin Nyan.Type) -> Bool // user: %7
で呼ばれているのがわかりますね。実際に__derived_enum_equals
のコードと思われる場所を見てみると以下のようになっていることがわかります。
// static Nyan.__derived_enum_equals(_:_:)
sil hidden @$S1A4NyanO21__derived_enum_equalsySbAC_ACtFZ : $@convention(method) (Nyan, Nyan, @thin Nyan.Type) -> Bool {
// %0 // users: %9, %3
// %1 // users: %29, %4
// %2 // user: %5
bb0(%0 : $Nyan, %1 : $Nyan, %2 : $@thin Nyan.Type):
debug_value %0 : $Nyan, let, name "a", argno 1 // id: %3
debug_value %1 : $Nyan, let, name "b", argno 2 // id: %4
debug_value %2 : $@thin Nyan.Type, let, name "self", argno 3 // id: %5
%6 = alloc_box ${ var Int }, var, name "index_a" // user: %7
%7 = mark_uninitialized [var] %6 : ${ var Int } // users: %56, %8
%8 = project_box %7 : ${ var Int }, 0 // users: %47, %22, %14
switch_enum %0 : $Nyan, case #Nyan.hoge!enumelt: bb1, case #Nyan.fuga!enumelt: bb2 // id: %9
最後の行を見てみましょう。switch_enum
で1つめの引数%0
によって処理を分岐させており、
-
case #Nyan.hoge!enumelt:
(つまりcase hoge
)の場合はラベルbb1
へ、 -
case #Nyan.fuga!enumelt:
(つまりcase fuga
)の場合はラベルbb2
へ、
処理を引き継いでいます。bb1
とbb2
では、各処理の最後にラベルbb3
へ飛び、最後にswitch_enum
で2つめの引数%1
を比較して処理を分岐している事がわかります。
bb1: // Preds: bb0
%10 = metatype $@thin Int.Type // user: %13
%11 = integer_literal $Builtin.Int2048, 0 // user: %13
// function_ref Int.init(_builtinIntegerLiteral:)
%12 = function_ref @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %13
%13 = apply %12(%11, %10) : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %15
%14 = begin_access [modify] [unknown] %8 : $*Int // users: %16, %15
assign %13 to %14 : $*Int // id: %15
end_access %14 : $*Int // id: %16
br bb3 // id: %17
bb2: // Preds: bb0
%18 = metatype $@thin Int.Type // user: %21
%19 = integer_literal $Builtin.Int2048, 1 // user: %21
// function_ref Int.init(_builtinIntegerLiteral:)
%20 = function_ref @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %21
%21 = apply %20(%19, %18) : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %23
%22 = begin_access [modify] [unknown] %8 : $*Int // users: %24, %23
assign %21 to %22 : $*Int // id: %23
end_access %22 : $*Int // id: %24
br bb3 // id: %25
bb3: // Preds: bb2 bb1
%26 = alloc_box ${ var Int }, var, name "index_b" // user: %27
%27 = mark_uninitialized [var] %26 : ${ var Int } // users: %55, %28
%28 = project_box %27 : ${ var Int }, 0 // users: %50, %42, %34
switch_enum %1 : $Nyan, case #Nyan.hoge!enumelt: bb4, case #Nyan.fuga!enumelt: bb5 // id: %29
これ以降の解説は割愛しますが、これらから私達が以前まで書いていたswitch
による==
の実装と同様の実装を、コンパイラが生成していると考えられます!
まとめ
以上のことから以下のことがわかりました!
- Swift4.1では、Equatableの
static public func ==(lhs:rhs:) -> Bool
はコンパイラによって生成されている。 -
enum
の場合、今まで作っていたswitch-case
と同じような比較をコンパイラが生成してくれていると考えられる。
新しいEquatable
では、従来私達が書いていた==
による比較をコンパイラが生成してくれていることがわかりました!
これから先、このEquatable
で私達のコーディングライフがもっと楽しくなればいいですね!
今回のこの機能の実装と解説はSynthesizing Equatable and Hashable conformanceで見れますので、チェックしてみるのも良いかもしれません!
間違い等があれば、コメントをよろしくおねがいします!