TL;DR
- 同じ
intでもCustomerIdとOrderIdは別物なので、プリミティブ値を単純型で包んで区別する。 - 単純型にはメモリと速度のコストがあるので、必要なら型エイリアスや構造体で代替する。
内容
単一ケースの判別共用体
ProductCode や OrderId は、どちらも int で表せても互換性はない。型が違うことを明確にするため、プリミティブな値をラップする。F# では、ケースが 1 つだけの判別共用体(単純型)を使う。
type CustomerId = CustomerId of intケース名をコンストラクタ関数として使うと、値を作れる。
let customerId = CustomerId 42こうすると、異なる型を誤って混同するとコンパイルエラーになる。
type CustomerId = CustomerId of int
type OrderId = OrderId of int
let customerId = CustomerId 42
let orderId = OrderId 42
printfn "%b" (orderId = customerId) // コンパイルエラー
let processCustomerId (id : CustomerId) = ...
processCustomerId orderId // コンパイルエラー中の値を取り出すときは、ケースラベルでパターンマッチをする。関数のパラメータで直接取り出すこともできる。
let customerId = CustomerId 42
let (CustomerId innerValue) = customerId
printfn "%i" innerValue // 42
// val processCustomerId: CustomerId -> unit
let processCustomerId (CustomerId innerValue) =
printfn "innerValue is %i" innerValueパフォーマンスの問題
単純型は、メモリ使用量と効率を犠牲にする。アプリケーションの種類によっては注意が必要である。代替策は 3 つある。
1 つ目は型エイリアスである。オーバーヘッドはないが、型の安全性は下がる。
type UnitQuantity = int2 つ目は値型(構造体)である。オーバーヘッドは残るが、配列に入れると連続したメモリ領域に置かれるので、キャッシュの効率がよい。
[<Struct>]
type UnitQuantity = UnitQuantity of int3 つ目は、単純型のコレクションではなく、プリミティブ値のコレクション全体を 1 つの型として定義する方法である。
type UnitQuantities = UnitQuantities of int[]ただし一般には、まず最も分かりやすい方法でドメインをモデリングし、その後でチューニングや最適化に取り組むのがよい。