測定単位 (F#)
F# の浮動小数点値と符号付き整数値には測定単位を関連付けることができます。測定単位は一般に、長さ、体積、質量などを示すために使用されます。単位のある数値を使用すると、演算関係に正しい単位があることをコンパイラで確認できるようになり、プログラミング エラーを回避するのに役立ちます。
[<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^-2 と m /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>
測定単位は、コンパイル時の測定単位のチェックに使用されますが、実行時環境では保持されません。したがって、パフォーマンスには影響しません。
測定単位は、浮動小数点型だけではなく、どの型にも適用できますが、次元設定された数値をサポートしているのは、浮動小数点型、符号付き整数型、および Decimal 型だけです。したがって、プリミティブ型と、これらのプリミティブ型が含まれる集約に対してのみ、測定単位を使用する効果があります。
測定単位の使用例を次に示します。
// 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
// 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 mutable floatValue = 0.
if System.Double.TryParse(input, &floatValue)
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.
ジェネリック単位の使用
関連する測定単位を持つデータを処理するジェネリック関数を記述できます。これを行うには、次のコード例に示すように、型パラメーターとしてのジェネリック単位と共に型を指定します。
// 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
ジェネリック単位を使用した集約型の作成
次のコードは、ジェネリックである単位を持つ個々の浮動小数点値で構成される集約型を作成する方法を示します。これにより、さまざまな単位を使用する 1 つの型を作成できます。また、ジェネリック単位は、ある単位のセットを持つジェネリック型を、異なる単位のセットを持つ同じジェネリック型とは別の型にすることでタイプ セーフを保持します。この手法の基本は、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 で乗算します。ただし、相互運用レイヤーを記述する場合は、いくつかの明示的な関数を使用して、単位のない値を単位のある値に変換できます。これらの関数は、Microsoft.FSharp.Core.LanguagePrimitives モジュールに含まれています。たとえば、次のコードに示すように、単位のない float を float<cm> に変換するには、FloatWithMeasure を使用します。
open Microsoft.FSharp.Core
let height:float<cm> = LanguagePrimitives.FloatWithMeasure x
F# Power Pack の測定単位
F# Power Pack には単位ライブラリが用意されています。単位ライブラリには、SI 単位と物理定数が含まれます。