Midoliy |> F# - レコード型
レコード型
Midoliy|F#プログラミング
レコード型とは
簡単な利用例
レコード式
相互再帰レコード
レコードとクラスの違い

レコード型とは


 レコード型(= record)とは、名前付きの単純な値の集合を表すことができます。また、F# 4.1 以降ではレコード型を参照型としても値型としても宣言可能です。既定ではレコード型は参照型となっています。
// ------------------
// [ 構文 ]
// ------------------

[<attribute>]
type [ accessibility-modifier ] type-name = 
    {
        [ mutable ] label1 : type1;
        [ mutable ] label2 : type2;
        ...
        [ mutable ] label_N : type_N;
    }
    [ member-list ]
                

 レコード型の一つひとつの要素名を ラベル(= label) といい、要素自体を フィールド(= field) といいます。各ラベルは {} で囲まれた場所に宣言し、その宣言時に型情報(= type)を付与します。また、レコード型にはメンバ(= member)を宣言することも可能です。
 これだけ見ると クラス(= class) や 構造体(= struct) と似ていますね?実際、C#側からだとこのレコードはクラスとして認識されます。ただし、そのクラスは sealed されているクラスとして認識されるため、継承などはできません。継承を考える場合はクラスや構造体を利用しなければならないことに注意が必要です。


簡単な利用例


 "論よりコード" ということで、レコード型の宣言例を見ていきましょう。


 例のように、レコード型にメンバを宣言することも簡単です。メンバ宣言は {} の外側 で行うことに注意してください。

 では、このレコード型を使ってみましょう。レコード型を使う場合は、レコード式 と呼ばれる式を利用します。レコード式は let束縛 時に {} の中で各レコードフィールドを初期化することで、レコード型の値を得ることができます。また、初期化されたフィールドの内容を元にコンパイラが自動で型推論をしてくれます。
 以下はレコード式の簡単なサンプルになります。


 ここで疑問が一つ湧いてきましたね?同一名称・同一構成のレコードラベルを持つ別レコードが存在した場合にコンパイラは型推論をどうするのでしょうか?

 そういった場合は、基本的に最後に宣言されたレコード型が優先されます。もし仮に、最初 or 途中で宣言したレコード型として推論させたい場合は、ラベルに対してレコード型名を付与することで明示的に特定のレコード型に推論させることが可能です。



レコード式


 "簡単な利用例" ではレコード型の値を新規作成するためのレコード式を紹介しました。レコード式には既存のレコード値をコピー・更新する機能を持っています。これは、コピー&更新レコード式 と呼ばれている機能です。コピー&更新レコード式だと名称が長い上にわかりにくいため、当サイトでは レコード更新式 という名称を採用することにします。
 レコード型はデフォルトでimmutableです。しかし、レコード更新式を使用することによって、変更されたレコード値を簡単に作成することができます。

レコード更新式

 前述したとおりレコード型はimmutableなので、既存のレコードを更新することはできません。更新されたレコードを作成するには、レコードのすべてのレコードフィールドをもう一度指定して作成しなおさなければなりません。この「すべてのレコードフィールドをもう一度指定しなおさなければならない」という煩雑な作業をレコード更新式を使用することで簡略化することができます。
 レコード更新式は、既存のレコード値を引数に取り、更新したいフィールドを再定義することで新しいレコードを作成することができます。


   ここで注目すべきは、レコード更新式を使ったとしても元のレコードには影響がないことです。上記のサンプルを見てもわかる通り、元レコードに影響がありませんね。つまり、レコード値のimmutable性は保たれているということです。これは非常に大きなメリットでもありますので、頭の片隅にでも置いておくとよいでしょう。


相互再帰レコード


 レコード型を宣言するときに、あとで定義した別の型に依存させたい場合があります。しかしF#では通常、依存させたい型や関数がある場合には、先に宣言をしていなければなりません。これはちょうどC/C++などで関数の前方宣言が必要なことに似ています。
 そんな制約を回避するための手法が 相互再帰レコード です。これは型宣言を and で繋げることで2つ以上のレコード型をリンクさせることができます。


 相互再帰レコードの使用頻度はそれほど高くないかもしれませんが、使えるようになっておくと何かと便利な機能なので、マスターしておきましょう。


レコードとクラスの違い


 クラスとは違いレコードフィールドは自動的にプロパティとして公開されます。また、レコードの初期化やコピーに使用されるという点でクラスとは異なります。レコードの型構成もクラスの型構成と異なる点がいくつかあります。
 まず、レコード型ではコンストラクタを定義することはできません。代わりに、レコード式を利用して値を作成します。また、クラスはコンストラクタのパラメータとフィールド、プロパティの間には直接の関係はありません。反対にレコードは初期化するときの値が直接フィールドへと束縛されるため直接的に関係していると言えるでしょう。

 次にレコード型は 判別共用体型構造体型 と同じように 構造的等価セマンティックス を持ちます。反対にクラスの場合は 参照等価セマンティックス を持ちます。
 もう少しわかりやすく言うならば、「構造的等価セマンティクス」とは "値が同じ値ならば同じものと判断する" 性質のことであり、「参照等価セマンティクス」とは "比較した値のアドレスが同じならば同じものと判断する" 性質のことです。F#やC#をやっているとあまりアドレスを意識しない人もいるかもしれませんが、やはりメモリのアドレスなどの知識はあるとプログラミングに大いに役立つため一度勉強してみるのもよいかもしれません。ここでは本題から離れてしまうためメモリアドレスの解説はいたしません。