Unidades de medida

Los valores de punto flotante y enteros con signo en F# pueden tener unidades de medida asociadas, que se utilizan normalmente para indicar la longitud, el volumen, masa, etc. Mediante el uso de cantidades con unidades, se habilita el compilador para comprobar que las relaciones aritméticas tienen las unidades correctas, lo que ayuda a evitar errores de programación.

Nota:

Estos ejemplos demuestran la corrección en cálculos aritméticos que involucran unidades de medida, la característica también puede ser aprovechada para agregar anotaciones de tipo seguro con cero costos de representación a otros tipos, con un enfoque como el proyecto FSharp.UMX.

Sintaxis

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

Comentarios

La sintaxis anterior define el nombre de unidad como una unidad de medida. La parte opcional se usa para definir una nueva medida en términos de unidades definidas previamente. Por ejemplo, la línea siguiente define la medida cm (centímetros).

[<Measure>] type cm

La siguiente línea define la medida ml (mililitro) como un centímetro cúbico (cm^3).

[<Measure>] type ml = cm^3

En la sintaxis anterior, measure es una fórmula que implica unidades. En las fórmulas que implican unidades, se admiten potencias integrales (positivas y negativas), los espacios entre unidades indican un producto de las dos unidades, * también indica un producto de unidades y / indica un cociente de unidades. Para una unidad recíproca, puede usar una potencia de entero negativo o un / que indique una separación entre el numerador y el denominador de una fórmula unitaria. Varias unidades del denominador deben estar rodeadas por paréntesis. Las unidades separadas por espacios después de / se interpretan como parte del denominador, pero las unidades que siguen a * se interpretan como parte del numerador.

Puede usar 1 en expresiones unitarias, ya sea solo para indicar una cantidad sin dimensiones o junto con otras unidades, como en el numerador. Por ejemplo, las unidades de una tasa se escribirían como 1/s, donde s indica segundos. Los paréntesis no se usan en fórmulas unitarias. No se especifican constantes de conversión numéricas en las fórmulas de unidad; sin embargo, puede definir constantes de conversión con unidades por separado y usarlas en cálculos comprobados por unidad.

Las fórmulas unitarias que significan lo mismo se pueden escribir de varias maneras equivalentes. Por lo tanto, el compilador convierte las fórmulas unitarias en una forma coherente, que convierte las potencias negativas en recíprocas, agrupa unidades en un solo numerador y un denominador, y ordena alfabéticamente las unidades del numerador y el denominador.

Por ejemplo, las fórmulas de unidad kg m s^-2 y m /s s * kg se convierten en kg m/s^2.

Las unidades de medida se usan en expresiones de punto flotante. El uso de números de punto flotante junto con unidades de medida asociadas agrega otro nivel de seguridad de tipo y ayuda a evitar los errores de coincidencia de unidad que pueden producirse en las fórmulas cuando se usan números de punto flotante débilmente tipados. Si escribe una expresión de punto flotante que usa unidades, las unidades de la expresión deben coincidir.

Puede anotar literales con una fórmula unitaria entre corchetes angulares, como se muestra en los ejemplos siguientes.

1.0<cm>
55.0<miles/hour>

No se coloca un espacio entre el número y el corchete angular; sin embargo, puede incluir un sufijo literal como f, como en el ejemplo siguiente.

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

Esta anotación cambia el tipo del literal de su tipo primitivo (como float) a un tipo dimensionado, como float<cm> o , en este caso, float<miles/hour>. Una anotación unitaria de <1> indica una cantidad sin dimensiones y su tipo es equivalente al tipo primitivo sin un parámetro de unidad.

El tipo de una unidad de medida es un tipo entero con signo o punto flotante junto con una anotación de unidad adicional, indicada entre corchetes. Por lo tanto, al escribir el tipo de una conversión de g (gramos) a kg (kilogramos), se describen los tipos como se indica a continuación.

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

Las unidades de medida se usan para la comprobación de unidades en tiempo de compilación, pero no se conservan en el entorno en tiempo de ejecución. Por lo tanto, no afectan al rendimiento.

Las unidades de medida se pueden aplicar a cualquier tipo, no solo a tipos de punto flotante; sin embargo, solo los tipos de punto flotante, los tipos enteros firmados y los tipos decimales admiten cantidades dimensionadas. Por lo tanto, solo tiene sentido usar unidades de medida en los tipos primitivos y en agregados que contienen estos tipos primitivos.

En el siguiente ejemplo se muestra el uso de las unidades de medida.

// 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

En el ejemplo de código siguiente se muestra cómo convertir de un número de punto flotante sin dimensiones a un valor de punto flotante dimensionado. Simplemente se multiplica por 1,0, aplicando las dimensiones a la 1,0. Puede abstraer esto en una función como degreesFahrenheit.

Además, cuando se pasan valores de dimensión a funciones que esperan números de punto flotante sin dimensiones, debe cancelar las unidades o convertir a float mediante el operador float. En este ejemplo, se divide por 1.0<degC> para los argumentos en printf porque printf espera cantidades sin dimensiones.

[<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."

En la sesión de ejemplo siguiente se muestran las salidas de y las entradas de este código.

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

Tipos primitivos que admiten unidades de medida

Los siguientes tipos o alias de abreviatura de tipo admiten anotaciones de unidades de medida:

Alias F# Tipo 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

Por ejemplo, puede anotar un entero sin signo de la siguiente manera:

[<Measure>]
type days

let better_age = 3u<days>

La adición de tipos enteros sin signo a esta característica se documenta en F# RFC FS-1091.

Unidades de medida predefinidas

Hay disponible una biblioteca de unidades en el espacio de nombres FSharp.Data.UnitSystems.SI. Incluye unidades si en su forma de símbolo (como m para medidor) en el subespacio de nombres UnitSymbols y en su nombre completo (como meter para medidor) en el subespacio de nombres UnitNames.

Uso de unidades genéricas

Puede escribir funciones genéricas que operan en datos que tienen una unidad de medida asociada. Para ello, especifique un tipo junto con una unidad genérica como parámetro de tipo, como se muestra en el ejemplo de código siguiente.

// 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

Creación de tipos de colección con unidades genéricas

En el código siguiente se muestra cómo crear un tipo de agregado que consta de valores de punto flotante individuales que tienen unidades genéricas. Esto permite crear un único tipo que funcione con una variedad de unidades. Además, las unidades genéricas conservan la seguridad de tipos asegurándose de que un tipo genérico que tiene un conjunto de unidades es un tipo diferente al del mismo tipo genérico con un conjunto diferente de unidades. La base de esta técnica es que el atributo Measure se puede aplicar al parámetro de tipo.

 // 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> }

Unidades en runtime

Las unidades de medida se usan para la comprobación de tipos estáticos. Cuando se compilan valores de punto flotante, se eliminan las unidades de medida, por lo que las unidades se pierden en runtime. Por lo tanto, cualquier intento de implementar la funcionalidad que depende de comprobar las unidades en runtime no es posible. Por ejemplo, no es posible implementar una funciónToString para imprimir las unidades.

Conversiones

Para convertir un tipo que tiene unidades (por ejemplo, float<'u>) en un tipo que no tiene unidades, puede usar la función de conversión estándar. Por ejemplo, puede usar float para convertir float en un valor que no tiene unidades, como se muestra en el código siguiente.

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

Para convertir un valor sin unidades en un valor que tenga unidades, puede multiplicar por un valor de 1 o 1,0 anotado con las unidades adecuadas. Sin embargo, para escribir capas de interoperabilidad, también hay algunas funciones explícitas que puede usar para convertir valores sin unidad en valores con unidades. Se encuentran en el módulo FSharp.Core.LanguagePrimitives. Por ejemplo, para convertir de un float sin unidad a float<cm>, use FloatWithMeasure, como se muestra en el código siguiente.

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

Vea también