##タプル(組)
順序付きのデータ集合。
引数をまとめたり、複数の戻り値をさっと返したいときとか、色々と使える。
特に.NETの形式にそって関数やメソッドを定義するには、タプルにする必要がある。
ただし、型シグネチャを見ただけでは(string * stringなど)
実際に何のデータが入っているのか?は文脈読まないとわからなかったりするのが難点。
// タプルを指定
let sisterSL = ("C12", "C56") // string * string
let tank = ("tendar", 600) // string * int
// fstとsndで要素を取り出せる
fst sisterSL |> printfn "%A" // => "C12"
snd sisterSL |> printfn "%A" // => "C56"
// letで別々の値に取り出して束縛することが出来る
let tankType, water = tank
printfn "%A" tankType // => "tender"
printfn "%A" water // => 600
##判別共用体
定義値のうちどれかの値しか取れないので、パターンマッチで網羅チェックができる。
また、定義値の要素それぞれ別々に型を指定する事ができる。
汎用的に使えるが、これもタプルと同じく値の意味は文脈読まないとわからなかったりする。
// 各要素の型を指定できる
type Implezza =
| GC8 of string
| GDA of int
| GDB
// メソッドやプロパティも持たせられる
member x.Run = printfn "go!"
// 引数渡さなくても生成できるが、部分適用されてるだけなので型が違う
let nonGc8 = Implezza.GC8 // string -> Implezza
// これで実体化できる
let geGc8 = Implezza.GC8("GE") // Implezza
let plainGc8 = Implezza.GC8("") // Implezza
let gfGc8 = Implezza.GC8("GF") // Implezza
// 異なる型は渡せない
// let gda = Implezza.GDA("A")
let gda = Implezza.GDA(2)
// gdbに引数を渡すとコンパイルエラーになる
// let gdb = Implezza.GDB(2)
let gdb = Implezza.GDB
パターンマッチは_で書くことも出来るが、将来パターンが増えた時の事を考えると
あえて使わず、網羅したほうが良いと思われる。
// GC8のグレードを判定するパターンマッチ
// ここではGDAとGDBが来たとき用にワイルドカードのパターンマッチを指定しているが
// 将来的にパターンが増えた時に全部_に当たるので修正を忘れてしまうかも
let getGC8Grade (imp:Implezza) =
match imp with
| GC8("GA") -> "ver.I"
| GC8("GB") -> "ver.II"
| GC8("GC") -> "ver.III"
| GC8("GD") -> "ver.IV"
| GC8("GE") -> "ver.V"
| GC8("GF") -> "ver.VI"
| GC8(_) -> "unknown grade"
| _ -> "not gc8"
// 明示的にパターンを書いた方が、コンパイラの網羅チェックで怒ってくれるので
// 修正漏れを防ぎやすい
let getGC8GradeExpect (imp:Implezza) =
match imp with
| GC8("GA") -> "ver.I"
| GC8("GB") -> "ver.II"
| GC8("GC") -> "ver.III"
| GC8("GD") -> "ver.IV"
| GC8("GE") -> "ver.V"
| GC8("GF") -> "ver.VI"
| GC8(_) -> "unknown grade"
| GDA(_)
| GDB -> "not gc8"
plainGc8 |> getGC8Grade |> printfn "%s"
geGc8 |> getGC8Grade |> printfn "%s"
gfGc8 |> getGC8Grade |> printfn "%s"
##レコード
タプルや判別共用体では値が特定の順番で入っているだけでその意味はわからない。
これを解決するのがレコード。
###利点
- 渡されるパラメータから型推論ができる。
- デフォルトで不変
- 継承不可
- 標準的なパターンマッチで使える(網羅性は問われない)
- わざわざEquals()を定義しなくても=で内容比較できる
// メソッドやプロパティも持てる
type Car =
{ Maker:string; Name:string }
member x.run = printfn "go!"
// フィールドを指定するので、順番が変わってもよい
let gc8 = {Name = "implezza"; Maker = "Subaru"}
// フィールドとして取り出せる
printfn "%A:%A" gc8.Maker gc8.Name
let wrx = {gc8 with Name = "implezza WRX"}
printfn "%A:%A" wrx.Maker wrx.Name
// パターンマッチ
let hasWRX = function
| {Name = "implezza WRX"} -> true
| _ -> false
gc8 |> hasWRX |> printfn "%b"
wrx |> hasWRX |> printfn "%b"
##列挙体
整数型のシンタックスシュガー。
なので、列挙してない値も作れたりできるし、値の意味に保証はない。
利用目的は単なる定数リストが欲しい時。
また、ただの値型なので判別共用体に比べると圧倒的にコストが低い。
// ただの定数リスト
type ChessPiece =
| Empty = 0
| Pawn = 1
| Knight = 3
| Bishop = 4
| Rook = 5
| Queen = 8
| King = 99
// 列挙名が示す値が欲しい時
int ChessPiece.Bishop |> printfn "%A"
// 生成はこんな感じで作れるが、列挙体に存在しない値も定義できる
let cs = [for i in 0..8 do yield enum<ChessPiece>(i)]
cs |> printfn "%A"
// => [Empty; Pawn; 2; Knight; Bishop; Rook; 6; 7; Queen]
##構造体
だいたいクラスと同じだが、こちらは参照型ではなく値型として作られる。
ヒープではなくスタックに詰まれるので、生成コスト的には有利ではあるが
引数として使ったりするとコピーが発生しまくるので、遅くなる場合もある。
小さいオブジェクトを大量に作ったりするなら構造体の方が向いている。
また、クラスはコンストラクタを必ず作る必要があり、定義したもの以外は作られない。
構造体では各フィールドにデフォルト値を設定するデフォルトコンストラクタが自動定義される。
(値型には0、参照型にはnull)
###制約
- letは使用不可
- 継承不可(暗黙で[]がつく)
- デフォルトコンストラクタをオーバーライドできない
[<Struct>]
type SealedSword =
// 変更可能なフィールドを作るには、valにする
val mutable swordIsBroken:bool
// 変更可能なフィールドを任意の値で初期化するにはコンストラクタを作る
new(broken:bool) = {swordIsBroken = broken}
member x.Slash(baseDamage, magnification) =
baseDamage * magnification
member x.Broken
with get() = x.swordIsBroken
and set(broken:bool) = x.swordIsBroken <- broken
// 可変値を含む構造体のインスタンスもmutableで作る必要がある
let mutable roy = SealedSword(false)
roy.Broken |> printfn "Broken? -> %A"
roy.Slash(100.0, 1.2) |> printfn "Slash! -> %A"
roy.Broken <- true
roy.Broken |> printfn "Broken? -> %A"