書いて覚えるSwift入門

第34回The value of the property

プロパティの(価)

前回予告したとおり、今回はカジュアルな話題です。題して⁠The value of the property⁠⁠。⁠プロパティの値」とも「プロパティの価値」とも邦訳できますが、その双方の意味を込めています。

しかしそもそも「プロパティ」って何でしょう?

iOSの辞書によると図1⁠、⁠財産」⁠不動産」⁠属性」⁠所有権」そして一周回って「プロパティ」と出てきますが、いずれも共通しているのは「固有の何か⁠⁠。たとえばファイル。ファイルの内容が「値」なら、ファイル名や更新日時などのメタデータは「プロパティ⁠⁠。⁠固有値」という訳がよさそうなのですが、この言葉は数学における⁠eigenvalue⁠という言葉に「取られて」しまったおかげか、ITの世界では「プロパティ」というカタカナで落ち着いたようです。

図1 iOSの辞書でプロパティを検索
図1 iOSの辞書でプロパティを検索

値の中の値

それではSwiftにおけるプロパティとはいったいなんでしょうか? 断言する前に実例を見てみましょう。Playgroundで次を入力してみてください。

"swift".description          // "swift"
(42).description              // "42"
(42.195).description      // "42.195"
[42].description              // "[42]"
["swift":4.1].description
           // "["swift": 4.0999999999999996]"

それぞれの値を文字列にしたものが""に囲まれて表示されているはずです。これがそれぞれの値(value)に対する.descriptionという名のプロパティ(property)で、英語のdescription=説明という名のとおり、それぞれの値の説明になっています。

今度は.description.hashValueにしてみましょう。StringIntDoubleではこのようにIntの値が出てきますが……、

"swift".hashValue   // 4799434080856491603
(42).hashValue       // 42
(42.195).hashValue  // 4631135235630652457

ArrayDictionaryだとエラーになります図2⁠。

図2 ArrayやDictionaryではエラー発生
[42].hashValue                 // error: value of type '[Int]' has no member 'hashValue'
["swift":4.1].hashValue   // error: value of type '[String : Double]' has no member 'hashValue'

どんな値がどんなプロパティを持つかは、(type)によって決まります。

今そこにあるプロパティ・その場で作られるプロパティ

プロパティは型によって決まる。具体的にその様子を見てみましょう。

struct Point<T:FloatingPoint> {
    var x:T
    var y:T
}

PointというStructを作ったうえで使ってみます。

var dp = Point(x:3,y:4)
dp.x // 3
dp.y // 4

.x.yというプロパティが存在し、初期化したとおりの値となっています。これをストアド・プロパティ(stored property)と言います。Cの構造体(struct)のメンバに相当します。

このPointに原点(0,0)から距離を返す.distanceというプロパティを付け加えてみましょう。

import Darwin // for sqrt()

extension Point {
    var distance:T {
        return sqrt(x*x + y*y)
    }
}

実行結果は次のとおり。

dp.distance // 5

メモリにそのまま保持されているストアド・プロパティに対し、演算された結果はコンピューテッド・プロパティ(computed property)と言います。

「あれ? これってメソッド(method)じゃね?」と思った読者のあなたは鋭い。実は本質的に同じことなのです。

extension Point {
    func getDistance() -> T {
        return sqrt(x*x + y*y)
    }
}

dp.getDistance() // 5

ここで.getDistance()()を取ってみましょう。エラーにはならず、() -> Doubleと出てくるはずです。

そうなのです。Swiftではメソッドもまた単なる関数型のストアド・プロパティに過ぎず、コンピューテッド・プロパティは()を付けずに実行されるメソッドにすぎないのです。

しかし、Swiftのコンピューテッド・プロパティにはそれ以上のことができます。ストアド・プロパティを上書きすることもできるのです。

.distanceの定義を次のように変えてみます。

extension Point {
    var distance:T {
        get {
            return sqrt(x*x + y*y)
        }
        set {
            let d = distance
            x *= newValue / d
            y *= newValue / d
        }
    }
}

すると……、

dp.distance = 10
dp.x // 6
dp.y // 8

これは、次と等価と言えるでしょう。

extension Point {
    func getDistance() -> T {
        return sqrt(x*x + y*y)
    }
    mutating func setDistance(_ newValue:T) {
        let d = self.getDistance()
        x *= newValue / d
        y *= newValue / d
    }
}

ほかの言語でも、値を取得するプロパティ(や言語によっては属性[attribute⁠⁠)をgetter、値を変更するプロパティをsetterと言いますが、Swiftのプロパティはその双方に対応しています。どんな場合にどちらを用いるべきかというのはスタイルの問題になりますが、Swiftはプロパティとメソッドの違いは()の有無に過ぎず、どちらもプロパティという立場をとっています。

添字もsubscriptという名のプロパティ

配列(array)や辞書(dictionary)から要素を取り出すとき、多くの言語では添字(subscript)で要素を指定します。これはSwiftも同様です。

[42][0]    // 0
["swift":4.1]["swift"] // 4.1

しかしよく考えてみると、これも一種のgetter/setterです。一般化できないのでしょうか? もちろんできます。そう、Swiftなら。

例として、IntBoolの配列として操作できるようにしてみます。

extension Int {
   subscript(_ i:Int)->Bool {
       get {
           return self & (1 << i) == 0
       }
       set {
           var mask = 1 << i
           if !newValue {
               mask = ~mask
               self &= mask
           } else {
               self |= mask
           }
       }
   }
} 

実行してみます。

var v = 0;
v[1] = true // v = 000010 == 2
v[3] = true // v = 001010 == 10
v[5] = true // v = 101010 == 42
v           // 42

Swiftではsubscriptというメソッド=プロパティを定義することによって、ほぼどんな型でも添字が使えるようになります。別の言い方をすると、添字もまたメソッドの構文糖衣(syntactic sugar)にすぎないということです。

ちなみにsubscriptというのはSwiftではキーワード扱いなので、

[42].subscript(0)

と書くことはできません。エラーになるべきなのですが、Xcode 9.2/Swift 4.0.3の現在は、図3のようになります。残念!

図3 .subscriptは禁呪
% swift
Welcome to Apple Swift version 4.0.3 (swiftlang-900.0.74.1 clang-900.0.39.2). Type :help for assistance.
  1> [42].subscript(0)
Segmentation fault

型もまた値。そして値には……

Rubyではクラス自体もオブジェクトですが、Swiftの型もまた値(value)になっています。しかしそのまま値として使えてしまうと何かと不便なので、値として使いたいときには.selfというプロパティを使います。

type(of:42) == Int.self   // true
type(of:42.0) == Int.self // false

何でもはできないわよ。できることだけ

こうしてみると、プロパティは型をも含めたSwiftのすべての値に存在するように見えます。ほぼ正解なのですが重大な例外が3つ存在します。

1つは、Optional

var v:Int? = nil
v.hashValue // error: value of optional type
'Int?' not unwrapped;

当然ながら、?!でunwrapせねばならず、そしてunwrapされた値はもはやOptionalではありません。Optionalの存在意義を考えれば、これは当然とも言えます。

もう1つは、Any

extension Any { // error: non-nominal type 'Any'
cannot be extended
}

何でも入るかわりにそのままでは何もできません。何もできないように組込みプロパティが存在しないだけではなく、extensionすることも禁止されています。使うときにはasなどで中身を取り出すという意味で、Optionalと同様に自然です。

そして最後に総称型(generic types⁠⁠。以前の連載でも触れましたが、

func id<T>(_ x:T)->T {
    return x
}

let idd<T>:(T)->T = { return $0 }

と書き直すことはできません。別の言い方をすると、Swiftの総称型には値が存在せず、具体的な型となってはじめて値を持つのです。[42]の型はArrayではなくArray<Int>であり、["swift":4.1]の型はDictionaryではなくDictionary<String,Double>であることを考えれば、これまた当然とも言えます。

まとめ

値があるところにプロパティがあり、型自身すら値を持つSwiftにおけるプログラミングとは、型にプロパティを植えることであると言っても過言ではありません。というわけでまとめます。

  • AnyおよびOptionalそして総称型以外のすべての値(value)にプロパティ(property)を持たせられる
  • プロパティもまた値
  • 型(type)自体も値(value)
  • プロパティは値をメモリに保持するストアド・プロパティ(stored property)だけではなく演算結果を返すコンピューテッド・プロパティ(computed property)がある
  • メソッド(method)は引数を0個以上取るコンピューテッド・プロパティ
  • 添字(subscript)は引数を1個以上取るメソッド
  • .subscriptという名のメソッドは禁則事項

(ほぼ)すべての値にプロパティを持たせられることは、静的型付けのSwiftが動的言語のようにカジュアルに書ける理由の1つです。いや、それ以上かもしれません。静的であるがゆえに型がわかった時点でどんなプロパティが存在するかは確定し、確定しているがゆえにXcodeでもREPLでもプロパティを補完することができるのですから。

次回もまた、Swiftがどうやって静的言語と動的言語のいいところどりをしているかを見ていくことにしましょう。

Software Design

本誌最新号をチェック!
Software Design 2022年9月号

2022年8月18日発売
B5判/192ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    MySQL アプリ開発者の必修5科目
    不意なトラブルに困らないためのRDB基礎知識
  • 第2特集
    「知りたい」⁠使いたい」⁠発信したい」をかなえる
    OSSソースコードリーディングのススメ
  • 特別企画
    企業のシステムを支えるOSとエコシステムの全貌
    [特別企画]Red Hat Enterprise Linux 9最新ガイド
  • 短期連載
    今さら聞けないSSH
    [前編]リモートログインとコマンドの実行
  • 短期連載
    MySQLで学ぶ文字コード
    [最終回]文字コードのハマりどころTips集
  • 短期連載
    新生「Ansible」徹底解説
    [4]Playbookの実行環境(基礎編)

おすすめ記事

記事・ニュース一覧