- Value Semantics とは
- Value Semantics を持たない型の問題と対処法
- Swift が値型中心の言語になれた理由とその使い方
Value Semantics とは
Value Semantics という用語は C++ などの言語で用いられることが多いようです。しかし、 Swift における Value Semantics は、それらとは少し異なるニュアンスを持っています。 Swift における Value Semantics については、 WWDC 2015 のセッション “Building Better Apps with Value Types in Swift” で詳しく説明されています。しかし残念ながら Value Semantics の定義については述べられていません。では Swift における Value Semantics の定義はどこにあるのでしょうか。それは、 Swift リポジトリの中のドキュメント “Value Semantics in Swift” です。
“Value Semantics in Swift” は 2013 年に書かれた古いドキュメントです。しかも、このドキュメントが収められた docs/proposals というディレクトリは、正式なドキュメントのためのものではありません。このドキュメントを信頼して良いのでしょうか。筆者は次の理由から、このドキュメントは信頼に足るものだと考えています。
- Swift Core Team の Dave Abrahams さんが著者であること
- 内容に WWDC のセッションとの一貫性があること
“Value Semantics in Swift” では、 Swift における Value Semantics を次のように定義しています。
For a type with value semantics, variable initialization, assignment, and argument-passing each create an independently modifiable copy of the source value that is interchangeable with the source.
(参考訳)ある型が Value Semantics を持っているとき、その型の変数を初期化したり、値を代入したり、引数に渡したりすると、元の値のコピーが作られて、そのコピーと元の値は独立に変更することができる。つまり、どちらかを変更してももう片方には影響を与えない。
コードを使って例を挙げてみます。
Value Semantics を持つ例
Stored Property を一つだけ持つシンプルな struct
、 Foo
を考えてみます。
struct Foo {
var value: Int = 0
}
この Foo
を使って次のような処理を考えます。
var a = Foo()
var b = a
a.value = 2
このとき、 b.value
も変更されるでしょうか。 Value Semantics を持つなら
どちらかを変更してももう片方には影響を与えない
ので、 a.value
を変更しても b.value
は変更されません。
結論から言うと、このケースでは b.value
は変更されません。なぜなら Foo
は 値型 だからです。
値型 のインスタンスは変数に直接格納されます。より具体的に言うと、インスタンスを表すバイト列が、変数を表すメモリ上の領域に直接書かれます。 値型 のインスタンスを変数に代入したときには、そのインスタンスを表すバイト列がコピーされて、代入先の変数の領域にそのまま書き込まれます。これが、代入によって 値型 のインスタンスがコピーされる仕組みです。
上記の例では、 var b = a
をした時点で a
の領域に格納されたバイト列が b
の領域にコピーされます。そのため、代入直後は a
と b
にまったく同じ内容のバイト列が書かれています。しかし、それらは内容が同じだけで異なる領域に書かれた二つのデータです。つまり、 a
と b
は別々の Foo
インスタンスを表しているということです。 a.value
を変更しても a
の領域の value
のバイト列が変更されるだけで、 b.value
が変更されることはありません。
この場合、 a
と b
は変更に対して独立である( a
と b
のどちらかに変更を加えてももう一方には影響を及ぼさない)ので、 Foo
は Value Semantics を持っていると言えます。
Value Semantics を持たない例
次に、 Value Semantics を持たない例を見てみます。先程のコードの struct
だった部分を class
に変更してみましょう。それ以外はまったく同じです。
class Foo {
var value: Int = 0
}
var a = Foo()
var b = a
a.value = 2
この場合は、 Foo
はクラスなので 参照型 です。そのため、 a.value
を変更することで b.value
も変更されてしまいます。
値型 と異なり、 参照型 のインスタンスは変数に直接格納されません。インスタンスの実体を表すバイト列はメモリ上の別の領域(ヒープ領域のどこか)に格納されていて、その領域を表すメモリのアドレスが変数に格納されます。たとえば、そのアドレスが 0x123DEF
だとすると、変数に実際に格納されているのは 0x123DEF
というアドレスを表すバイト列です。 var b = a
では、 a
に格納されたアドレス 0x123DEF
を表すバイト列が b
にコピーされます。このとき、 a
と b
は同じアドレス 0x123DEF
によって同一の Foo
インスタンスを参照することになります。そのため、 a.value
を変更すると b.value
も変更されてしまうわけです。
この例では変更に対する独立性を持たないので、 Foo
は Value Semantics を持ちません。
このように、片一方を変更するともう一方も変更され得る場合、 Value Semantics と対比して、その型は Reference Semantics を持っていると言われます。
Semantics vs Type
先の例や名前からも推測できる通り、 Value Semantics は 値型( Value Type ) と、 Reference Semantics は 参照型( Reference Type ) と深い関係があります。しかし、これらは同じものではありません。混同してしまわないように注意が必要です。
- Value Semantics ≠ 値型( Value Type )
- Reference Semantics ≠ 参照型( Reference Type )
たとえば、 値型 だけど Value Semantics を持たない型 や、 参照型 だけど Value Semantics を持つ型も存在します。 Value Semantics / Reference Semantics と、 値型 / 参照型 をきちんと区別して考えることが重要です。
値型だけど Value Semantics を持たない例
Semantics と Type を区別して考えるために、値型だけれども Value Semantics を持たない例を見てみましょう。
struct Foo
に加えて、 Bar
というクラスを導入します。
class Bar {
var value: Int = 0
}
この Bar
型のプロパティを Foo
に追加します。
struct Foo {
var value: Int = 0
var bar: Bar = Bar() // 👈 Bar 型のプロパティを追加
}
これを使って先程と似たようなことをしてみます。ただし、 a.value
を変更するのに加えて a.bar.value
も変更します。
var a = Foo()
var b = a
a.value = 2
a.bar.value = 3 // 👈
このとき、 Foo
は 値型 なので変数 a
, b
には独立した別々の Foo
インスタンスが格納されます。しかし、 Bar
は 参照型 なので、 a
と b
の bar
プロパティには同じ Bar
インスタンスのアドレスが格納され、そのアドレスを介して同じインスタンスを参照していることになります。
その状態で a.value
を変更しても、 a
と b
には異なる Foo
インスタンスが格納されているので b.value
には影響を与えません。しかし、 a.bar
と b.bar
は同じ Bar
インスタンスを参照しているので、 a.bar.value
に変更を加えると b.bar.value
も変更されます。
そのため、 Foo
は 値型 であるにも関わらず変更に対する独立性を持たない、つまり Value Semantics を持たないことになります。なお、 a.value
と b.value
は独立に変更できるので、この Foo
は Reference Semantics も持ちません。
この Foo
のように Value Semantics も Reference Semantics も持たない型は扱いづらく、そのような型を作ってしまわないように注意が必要です。そんな変な型を作ることはないと思うかもしれませんが、たとえば下記に挙げたようなクラスのインスタンスを 値型 のプロパティに持たせると、簡単に Value Semantics も Reference Semantics も持たない型ができあがってしまいます。
NSMutableArray
,NSMutableString
,NSMutableData
UILabel
,UISwitch
,UISlider
AVAudioPlayer
CMMotionManager
参照型プロパティを持つ値型でも Value Semantics を持つ例
先の例は、一見 参照型 のプロパティを持ったことが Value Semantics を失った原因のように思えます。しかし、 参照型 のプロパティを持つことで必ずしも Value Semantics が失われるわけではありません。次は、 参照型 のプロパティを持つ 値型 が Value Semantics を持つ例を見てみます。
先のコードの Bar
クラスを イミュータブルクラス に変更します。
final class Bar {
let value: Int = 0
}
Bar
クラスを イミュータブル にするために、 Bar
の value
プロパティを let
にして、 final class
に変更します。 final class
にするのは、 Bar
の ミュータブル なサブクラスが作られてしまうと Bar
型の イミュータビリティ が破壊されてしまうからです。
この Foo
と Bar
を使って先程と同じことをしてみます。そうすると、 Bar
は今 イミュータブルクラス なので、 a.bar.value
を変更しようとすると当然コンパイルエラーになります。
var a = Foo()
var b = a
a.value = 2
a.bar.value = 3 // ⛔
このケースでは、たしかに a.bar
と b.bar
は同じインスタンスを参照していますが、 Bar
は イミュータブル なので、そのインスタンスを通して状態を変更することはできません。そのため、 Bar
型のプロパティを持つことが Foo
インスタンスの変更に対する独立性を破壊することにはつながりません。結果として、 Foo
は 参照型 のプロパティを持つにも関わらず Value Semantics を持つということになります。
このようなケースはよく見られ、たとえば次のようなクラスのインスタンスをプロパティに保持しても Value Semantics を破壊する原因にはなりません。
NSNumber
,NSNull
UIImage
KeyPath
イミュータビリティと Semantics
イミュータブルクラス 自体の Semantics はどのように考えれば良いでしょうか。
次のような イミュータブルクラス Foo
を考えます。
final class Foo {
let value: Int = 0
}
この Foo
に対して、同様の処理を行います。
var a = Foo()
var b = a
a.value = 2 // ⛔
イミュータブルクラス のインスタンスはそもそも変更することができないので、片方を変更するともう片方も変更されるということは起こりません。 “Value Semantics in Swift” には、そのようなケースも Value Semantics を持つと書かれています。
また、 “Value Semantics in Swift” には興味深いことが書かれていて、 イミュータビリティ を持つ場合は Value Semantics と Reference Semantics が区別できないとあります。そのため、上記のような イミュータブル な Foo
クラスは Value Semantics と Reference Semantics を両方持つと言えます。また、 イミュータブル であれば、 値型 であっても Reference Semantics を持つと言えるでしょう。たとえば、次の Foo
は 値型 ですが Value Semantics と Reference Semantics の両方を持っています。
struct Foo {
let value: Int = 0
}
ミュータブルな参照型をプロパティに持つけど Value Semantics を持つ例
先程の例では、 ミュータブル な 参照型 のプロパティを持つ場合は Value Semantics を持ちませんでした。
struct Foo {
var value: Int = 0
var bar: Bar = Bar()
}
class Bar {
var value: Int = 0
}
しかし、「 ミュータブル な 参照型 のプロパティを持つ場合は Value Semantics を持たない」というようにパターンで判断するのは危険です。たとえば、標準ライブラリの Array
は内部に ミュータブル な 参照型 を保持していますが、 Copy-on-Write という仕組みを使って Value Semantics を実現しています。
Value Semantics を持つかどうかはパターンで判断するのではなく、定義に基づいて判断することが大切です。
まとめ
Swift における Value Semantics の定義は、ある型が Value Semantics を持つとき、その型の値が変更に対して独立であるということです。
値型 と Value Semantics 、 参照型 と Reference Semantics は同じものではなく、 値型 だからといって Value Semantics を持つとは限りませんし、 参照型 でも Value Semantics を持つこともあります。 Type と Semantics を区別して理解することが重要です。 Value Semantics を持つかどうかをパターンに当てはめて考えると、様々な例外を考慮しなければなりません。 Value Semantics を持つかどうかは定義に基づいて判断しましょう。