M.Hiroi's Home Page

お気楽 F# プログラミング超入門

構造体


Copyright (C) 2022 Makoto Hiroi
All rights reserved.

はじめに

「構造体 (structer)」はクラスのようにユーザーが定義するデータ型の一つです。クラスは「参照型」ですが、構造体は「値型」になります。値型は変数に代入するときや関数の引数に渡すとき、その値がコピーされます。基本的な数値型は値型です。構造体も同様に、内部の値がコピーされます。

●構造体の定義

構造体は type を使って宣言します。

1. type 名前 = struct
     // フィールド変数
     val [ mutable ] [ access-modifier ] 変数名: 型名
     ...
     // コンストラクタ
     new(...) = 式
     ...
     // メソッド
     member 自己識別子.名前(引数: 型, ...) = 式
     ...
   end

2. [<Struct>]
   type 名前 =
     // フィールド変数
     ...
     // コンストラクタ
     ...
     // メソッド
     ...

構造体は 1. のように type 名前 = struct ... end で定義します。属性 [<Struct>] を指定すると、struct と end を省略した軽量構文を使うことができます。フィールド変数は val を使って宣言します。クラスとは異なり、let 束縛や do 束縛はありません。構造体はクラスや他の構造体を継承することはできません。構造体を継承してサブクラスを定義することもできません。ただし、インターフェースは継承することができます。

構造体の値はクラスと同様にコンストラクタで生成します。

1. new 型名(...)
2. 型名(...)

C# の場合、クラスや構造体は 1. のように new を使ってインスタンスや値を生成します。本ページ (F# 入門) は、インスタンスを生成するとき new を使ってきましたが、F# は new を省略して 2. のように 型名(...) だけでもクラスのインスタンスや構造体の値を生成することができます。

フィールド変数はコンストラクタで初期化します。構造体は引数なしのコンストラクタを定義することはできません。引数なしでコンストラクタを呼び出すと、フィールド変数はゼロ初期化 (数値であれば 0 や 0.0、参照型であれば null) されます。val のアクセス制御は access-modifier で指定することができます。省略した場合は public になります。

●簡単な使用例

簡単な使用例として、平面座標を表す型 Point を構造体で定義してみましょう。

リスト : 構造体の使用例

type Point = struct
  val x: float
  val y: float
  new(a: float, b: float) = { x = a; y = b }
  member this.distance(p: Point) =
    let dx = this.x - p.x
    let dy = this.y - p.y
    sqrt(dx * dx + dy * dy)
end

プログラムは簡単なので説明は割愛させていただきます。以下に実行例を示します。

> type Point = struct
-   val x: float
-   val y: float
-   new(a: float, b: float) = { x = a; y = b }
-   member this.distance(p: Point) =
-     let dx = this.x - p.x
-     let dy = this.y - p.y
-     sqrt(dx * dx + dy * dy)
- end;;
[<Struct>]
type Point =
  new: a: float * b: float -> Point
  val x: float
  val y: float
  member distance: p: Point -> float

> let a = Point();;
val a: Point = FSI_0002+Point

> a.x;;
val it: float = 0.0

> a.y;;
val it: float = 0.0

> let b = Point(1.0, 1.0);;
val b: Point = FSI_0002+Point

> b.x;;
val it: float = 1.0

> b.y;;
val it: float = 1.0

> a.distance(b);;
val it: float = 1.414213562

●ジェネリック

構造体はクラスと同様にジェネリックを使用することができます。簡単な例として、2 つの値を格納する構造体 Pair を定義してみましょう。

リスト : ジェネリック構造体

type Pair<'a, 'b> = struct
  val mutable fst: 'a
  val mutable snd: 'b
  new(x: 'a, y: 'b) = {fst = x; snd = y}
end

プログラムは簡単なので説明は割愛させていただきます。以下に実行例を示します。

> type Pair<'a, 'b> = struct
-   val mutable fst: 'a
-   val mutable snd: 'b
-   new(x: 'a, y: 'b) = {fst = x; snd = y}
- end;;
[<Struct>]
type Pair<'a,'b> =
  new: x: 'a * y: 'b -> Pair<'a,'b>
  val mutable fst: 'a
  val mutable snd: 'b

> let a = Pair<int,float>();;
val a: Pair<int,float> = FSI_0011+Pair`2[System.Int32,System.Double]

> a.fst;;
val it: int = 0

> a.snd;;
val it: float = 0.0

> let b = Pair<int,float>(1, 1.2345);;
val b: Pair<int,float> = FSI_0011+Pair`2[System.Int32,System.Double]

> b.fst;;
val it: int = 1

> b.snd;;
val it: float = 1.2345

> let mutable c = Pair<int,float>();;
val mutable c: Pair<int,float> = FSI_0011+Pair`2[System.Int32,System.Double]

> c.fst;;
val it: int = 0

> c.fst <- 10;;
val it: unit = ()

> c.fst;;
val it: int = 10

構造体でフィールド変数を書き換える場合、フィールド変数を mutable で宣言するだけではなく、構造体の値を格納する変数も mutable で宣言する必要があります。ご注意くださいませ。

●値型のレコード

F# のレコードはデフォルトだと「参照型」ですが、属性 [<Struct>] を指定すると「値型」に変更することができます。簡単な例として、構造体 Pair をレコードで書き直してみましょう。最初は参照型のレコードです。

> type PairR<'a, 'b> = { mutable Fst: 'a; mutable Snd: 'b };;
type PairR<'a,'b> =
  {
    mutable Fst: 'a
    mutable Snd: 'b
  }

> let a = {Fst = 1; Snd = 1.234};;
val a: PairR<int,float> = { Fst = 1
                            Snd = 1.234 }

> a.Fst;;
val it: int = 1

> a.Fst <- 10;;
val it: unit = ()

> a;;
val it: PairR<int,float> = { Fst = 10
                             Snd = 1.234 }

レコード PairR のフィールド変数 Fst, Snd は mutable なので値を書き換えることができます。この場合、PairR は参照型なので、レコードを格納している変数 a が mutable でなくても値を書き換えることができます。

次は値型のレコードです。

> [<Struct>] type PairR<'a, 'b> = { mutable Fst: 'a; mutable Snd: 'b };;
[<Struct>]
type PairR<'a,'b> =
  {
    mutable Fst: 'a
    mutable Snd: 'b
  }

> let a = {Fst = 1; Snd = 1.234};;
val a: PairR<int,float> = { Fst = 1
                            Snd = 1.234 }

> a.Fst;;
val it: int = 1

> a.Fst <- 10;;
=> エラー

> let mutable b = {Fst = 123; Snd = 123.456};;
val mutable b: PairR<int,float> = { Fst = 123
                                    Snd = 123.456 }

> b.Fst;;
val it: int = 123

> b.Fst <- 456;;
val it: unit = ()

> b.Fst;;
val it: int = 456

属性 [<Struct>] を指定すると、PairR は値型のレコードになります。変数 a に格納されたレコードのフィールド変数を書き換えようとすると、変数 a が mutable でないためエラーが送出されます。変数 b のように mutable として宣言すると、値型のレコードのフィールド変数を書き換えることができます。


初版 2022 年 5 月 3 日