測定単位

F# の浮動小数点値と符号付き整数値には、測定単位を関連付けることができます。測定単位は、一般に、長さ、体積、質量などを示すために使用されます。 単位付きの数量を使用することにより、コンパイラで算術関係の単位が正しいことを確認できます。これにより、プログラミング エラーを防ぐことができます。

Note

これらの例は、測定単位を含む算術評価の正確性を示しています。この機能は、FSharp.UMX プロジェクトなどのアプローチを使用して、表現コストをかけずに、型セーフ注釈を他の型に追加するためにも利用できます。

構文

[<Measure>] type unit-name [ = measure ]

解説

前の構文では、unit-name が測定単位として定義されます。 省略可能な部分は、以前に定義した単位に基づいて新しいメジャーを定義するために使用されます。 たとえば、次の行では、メジャー cm (センチメートル) が定義されます。

[<Measure>] type cm

次の行では、メジャー ml (ミリリットル) が立方センチメートル (cm^3) として定義されます。

[<Measure>] type ml = cm^3

前の構文で、measure は単位が関係する数式です。 単位が関係する数式では、整数の累乗がサポートされ (正と負)、単位の間のスペースは 2 つの単位の積を示し、* も単位の積を示し、/ は単位の商を示します。 逆数単位の場合は、負の整数の累乗を使用するか、または単位の数式の分子と分母を区別する / を使用できます。 分母で複数の単位を指定する場合は、かっこで囲む必要があります。 / の後のスペースで区切られた単位は、分母の一部として解釈されますが、* の後に続く単位は、分子の一部として解釈されます。

単位式での 1 は、単独で無次元量を示すために、または分子などで他の単位と組み合わせて、使用することができます。 たとえば、レートの単位は 1/s と記述されます。ここで、s は秒を示します。 かっこは、単位式では使用されません。 単位式では数値変換定数を指定しません。ただし、単位を個別に指定して変換定数を定義し、単位がチェックされる計算でそれを使用できます。

同じことを意味する単位式を、さまざまな方法で記述できます。 したがって、コンパイラによって単位式は一貫性のある形式に変換されます。これにより、負の値は逆数に変換され、単位は 1 つの分子と分母にグループ化され、分子と分母の単位はアルファベット順にされます。

たとえば、単位式 kg m s^-2m /s s * kg は、どちらも kg m/s^2 に変換されます。

測定単位は浮動小数点式で使用します。 浮動小数点数を関連する測定単位と共に使用すると、タイプ セーフの別のレベルが追加され、弱く型指定された浮動小数点数を使用している場合に式で発生する可能性がある単位不一致エラーを回避するのに役立ちます。 単位を使用する浮動小数点式を記述する場合は、式の単位が一致している必要があります。

次の例に示すように、山かっこで囲んだ単位式でリテラルに注釈を付けることができます。

1.0<cm>
55.0<miles/hour>

数字と山かっこの間にスペースを入れないでください。ただし、次の例に示すように、f などのリテラル サフィックスを含めることはできます。

// The f indicates single-precision floating point.
55.0f<miles/hour>

このような注釈を使用すると、リテラルの型がプリミティブ型 (float など) から次元設定された型 (float<cm>、またはこの場合は float<miles/hour>) に変更されます。 <1> の単位注釈は無次元量を示し、その型は単位パラメーターを持たないプリミティブ型に相当します。

測定単位の型は、浮動小数点型または符号付き整数型であり、追加の単位注釈が角かっこで示されます。 したがって、g (グラム) から kg (キログラム) への変換の種類を書く場合は、次のように型を記述します。

let convertg2kg (x : float<g>) = x / 1000.0<g/kg>

測定単位は、コンパイル時の単位チェックに使用されますが、実行時の環境には保存されません。 そのため、パフォーマンスには影響しません。

測定単位は、浮動小数点型だけでなく、任意の型に適用できます。ただし、次元量がサポートされているのは、浮動小数点型、符号付き整数型、10 進数型のみです。 したがって、測定単位を使用する意味があるのは、プリミティブ型と、これらのプリミティブ型を含む集計のみです。

測定単位の使用例を次に示します。

// Mass, grams.
[<Measure>] type g
// Mass, kilograms.
[<Measure>] type kg
// Weight, pounds.
[<Measure>] type lb

// Distance, meters.
[<Measure>] type m
// Distance, cm
[<Measure>] type cm

// Distance, inches.
[<Measure>] type inch
// Distance, feet
[<Measure>] type ft

// Time, seconds.
[<Measure>] type s

// Force, Newtons.
[<Measure>] type N = kg m / s^2

// Pressure, bar.
[<Measure>] type bar
// Pressure, Pascals
[<Measure>] type Pa = N / m^2

// Volume, milliliters.
[<Measure>] type ml
// Volume, liters.
[<Measure>] type L

// Define conversion constants.
let gramsPerKilogram : float<g kg^-1> = 1000.0<g/kg>
let cmPerMeter : float<cm/m> = 100.0<cm/m>
let cmPerInch : float<cm/inch> = 2.54<cm/inch>

let mlPerCubicCentimeter : float<ml/cm^3> = 1.0<ml/cm^3>
let mlPerLiter : float<ml/L> = 1000.0<ml/L>

// Define conversion functions.
let convertGramsToKilograms (x : float<g>) = x / gramsPerKilogram
let convertCentimetersToInches (x : float<cm>) = x / cmPerInch

次のコード例では、無次元の浮動小数点数を次元のある浮動小数点値に変換する方法を示します。 1\.0 で乗算するだけで、1.0 に次元が適用されます。 これは、degreesFahrenheit のような関数に抽象化できます。

また、無次元の浮動小数点数を予期する関数に次元のある値を渡す場合は、単位を無効にするか、float 演算子を使用して float にキャストする必要があります。 この例では、printf で無次元量が想定されているため、printf への引数を 1.0<degC> で除算します。

[<Measure>] type degC // temperature, Celsius/Centigrade
[<Measure>] type degF // temperature, Fahrenheit

let convertCtoF ( temp : float<degC> ) = 9.0<degF> / 5.0<degC> * temp + 32.0<degF>
let convertFtoC ( temp: float<degF> ) = 5.0<degC> / 9.0<degF> * ( temp - 32.0<degF>)

// Define conversion functions from dimensionless floating point values.
let degreesFahrenheit temp = temp * 1.0<degF>
let degreesCelsius temp = temp * 1.0<degC>

printfn "Enter a temperature in degrees Fahrenheit."
let input = System.Console.ReadLine()
let parsedOk, floatValue = System.Double.TryParse(input)
if parsedOk
   then
      printfn "That temperature in Celsius is %8.2f degrees C." ((convertFtoC (degreesFahrenheit floatValue))/(1.0<degC>))
   else
      printfn "Error parsing input."

次のセッション例では、このコードからの出力とこのコードへの入力を示します。

Enter a temperature in degrees Fahrenheit.
90
That temperature in degrees Celsius is    32.22.

測定単位をサポートするプリミティブ型

次の型または型の省略形のエイリアスでは、測定単位の注釈がサポートされています。

F# のエイリアス CLR 型
float32/single System.Single
float/double System.Double
decimal System.Decimal
sbyte/int8 System.SByte
int16 System.Int16
int/int32 System.Int32
int64 System.Int64
byte/uint8 System.Byte
uint16 System.UInt16
uint/uint32 System.UInt32
uint64 System.UIn64
nativeint System.IntPtr
unativeint System.UIntPtr

たとえば、次のように符号なし整数に注釈を付けることができます。

[<Measure>]
type days

let better_age = 3u<days>

この機能への符号なし整数型の追加については、「F# RFC FS-1091」に記載されています。

定義済みの測定単位

単位ライブラリは FSharp.Data.UnitSystems.SI 名前空間で使用できます。 SI 単位は、UnitSymbols サブ名前空間のシンボル形式 (メートルの m など) と、UnitNames サブ名前空間のフルネーム (メートルの meter など) の両方に含まれています。

ジェネリック単位の使用

関連付けられた測定単位を持つデータを操作するジェネリック関数を作成できます。 これを行うには、次のコード例に示すように、型パラメーターとして型と共にジェネリック単位を指定します。

// Distance, meters.
[<Measure>] type m
// Time, seconds.
[<Measure>] type s

let genericSumUnits ( x : float<'u>) (y: float<'u>) = x + y

let v1 = 3.1<m/s>
let v2 = 2.7<m/s>
let x1 = 1.2<m>
let t1 = 1.0<s>

// OK: a function that has unit consistency checking.
let result1 = genericSumUnits v1 v2
// Error reported: mismatched units.
// Uncomment to see error.
// let result2 = genericSumUnits v1 x1

ジェネリック単位を使用したコレクション型の作成

次のコードでは、ジェネリックである単位を持つ個別の浮動小数点値で構成される集計型を作成する方法を示します。 これにより、さまざまな単位で動作する単一の型を作成できます。 また、ある単位セットを持つジェネリック型が、異なる単位セットを持つ同じジェネリック型とは異なる型であることを保証することで、ジェネリック単位はタイプ セーフを保持します。 この手法の基礎は、Measure 属性を型パラメーターに適用できることです。

 // Distance, meters.
[<Measure>] type m
// Time, seconds.
[<Measure>] type s

// Define a vector together with a measure type parameter.
// Note the attribute applied to the type parameter.
type vector3D<[<Measure>] 'u> = { x : float<'u>; y : float<'u>; z : float<'u>}

// Create instances that have two different measures.
// Create a position vector.
let xvec : vector3D<m> = { x = 0.0<m>; y = 0.0<m>; z = 0.0<m> }
// Create a velocity vector.
let v1vec : vector3D<m/s> = { x = 1.0<m/s>; y = -1.0<m/s>; z = 0.0<m/s> }

実行時の単位

測定単位は、静的な型チェックに使用されます。 浮動小数点値をコンパイルすると、測定単位が削除されるため、実行時には単位が失われます。 したがって、実行時の単位のチェックに依存する機能を実装しようとすることはできません。 たとえば、単位を出力する ToString 関数を実装することはできません。

コンバージョン

単位を持つ型 (float<'u> など) を単位を持たない型に変換するには、標準変換関数を使用します。 たとえば、次のコードに示すように、float を使用して、単位を持たない float 値に変換することができます。

[<Measure>]
type cm
let length = 12.0<cm>
let x = float length

単位なしの値を単位のある値に変換するには、適切な単位で注釈が付けられた 1 または 1.0 の値を乗算します。 ただし、相互運用性レイヤーを記述する場合は、単位なしの値を単位のある値に変換するために使用できる明示的な関数がいくつもあります。 これらは、FSharp.Core.LanguagePrimitives モジュールにあります。 たとえば、単位なしの float から float<cm> に変換するには、次のコードに示すように、FloatWithMeasure を使用します。

open Microsoft.FSharp.Core
let height:float<cm> = LanguagePrimitives.FloatWithMeasure x

関連項目