Unidades de medida

Valores de ponto flutuante e do inteiro com sinal no F# podem ter unidades de medida associadas, que normalmente são usadas para indicar o comprimento, o volume, a massa e assim por diante. Usando quantidades com unidades, o compilador pode verificar se as relações aritméticas têm as unidades corretas, o que ajuda a evitar erros de programação.

Observação

Esses exemplos demonstram a correção em cálculos aritméticos envolvendo unidades de medida. O recurso também pode ser aproveitado para adicionar anotação segura de tipo sem custos de representação a outros tipos, com uma abordagem como a do projeto FSharp.UMX.

Sintaxe

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

Comentários

A sintaxe anterior define unit-name como uma unidade de medida. A parte opcional é usada para definir uma nova medida em termos de unidades definidas anteriormente. Por exemplo, a linha a seguir define a medida cm (centímetro).

[<Measure>] type cm

A linha a seguir define a medida ml (mililitro) como um centímetro cúbico (cm^3).

[<Measure>] type ml = cm^3

Na sintaxe anterior, a medida é uma fórmula que envolve unidades. Em fórmulas que envolvem unidades, há suporte para potências integrais (positivas e negativas), os espaços entre unidades indicam um produto das duas unidades, * também indica um produto de unidades e / indica um quociente de unidades. Para uma unidade recíproca, você pode usar um número inteiro negativo elevado à potência ou / que indica uma separação entre o numerador e o denominador de uma fórmula de unidade. Várias unidades no denominador devem ficar entre parênteses. As unidades separadas por espaços após / são interpretadas como sendo parte do denominador, mas todas as unidades que seguem um * são interpretadas como sendo parte do numerador.

Você pode usar um nas expressões de unidade, somente para indicar uma quantidade sem dimensão ou junto com outras unidades, como no numerador. Por exemplo, as unidades de uma taxa seriam gravadas como 1/s, onde s indica segundos. Parênteses não são usados nas fórmulas de unidade. Constantes de conversão numérica não são especificadas nas fórmulas de unidade; no entanto, é possível definir constantes de conversão com unidades separadamente e usá-las em cálculos verificados por unidade.

As fórmulas de unidade que significam a mesma coisa podem ser gravadas de várias maneiras equivalentes. Portanto, o compilador converte as fórmulas de unidade em uma forma consistente, que converte potências negativas em recíprocos, agrupa unidades em um único numerador e um denominador e alfabetiza as unidades no numerador e no denominador.

Por exemplo, as fórmulas de unidade kg m s^-2 e m /s s * kg são convertidas em kg m/s^2.

As unidades de medida são usadas em expressões de ponto flutuante. O uso de números de ponto flutuante junto com unidades de medida associadas, adiciona outro nível de segurança de tipos e ajuda a evitar erros de incompatibilidade de unidade que podem ocorrer em fórmulas ao usar números de ponto flutuante com tipo fraco. Se você gravar uma expressão de ponto flutuante que usa unidades, as unidades na expressão deverão corresponder.

É possível anotar literais com uma fórmula de unidade em colchetes angulares, conforme mostrado nos exemplos a seguir.

1.0<cm>
55.0<miles/hour>

Você não deve inserir um espaço entre o número e o colchete angular; no entanto, você é possível incluir um sufixo literal como f no exemplo a seguir.

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

Tal anotação altera o tipo do literal de seu tipo primitivo (como float) para um tipo dimensionado, como float<cm> ou, nesse caso, float<miles/hour>. Uma anotação de unidade de <1> indica uma quantidade sem dimensão e seu tipo é equivalente ao tipo primitivo sem um parâmetro de unidade.

O tipo de uma unidade de medida é um ponto flutuante ou um tipo integral com sinal junto com uma anotação de unidade extra, indicada entre colchetes. Assim, ao gravar o tipo de uma conversão de g (gramas) para kg (quilogramas), os tipos são descritos da seguinte maneira.

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

As unidades de medida são usadas para verificação de unidade em tempo de compilação, mas não são mantidas no ambiente em tempo de execução. Portanto, elas não afetam o desempenho.

As unidades de medida podem ser aplicadas a qualquer tipo, não apenas a tipos de ponto flutuante; no entanto, apenas os tipos de ponto flutuante, tipos integrais assinados e tipos decimais dão suporte a quantidades dimensionadas. Portanto, só faz sentido usar unidades de medida nos tipos primitivos e em agregações que contêm esses tipos primitivos.

O exemplo a seguir ilustra o uso das 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

O exemplo de código a seguir ilustra a conversão de um número de ponto flutuante sem dimensão em um valor de ponto flutuante dimensionado. Basta multiplicar por 1.0, aplicando as dimensões a 1.0. Isso pode ser abstraído em uma função como degreesFahrenheit.

Além disso, quando você passa valores dimensionados para funções que esperam números de ponto flutuante sem dimensão, você deve cancelar as unidades ou converter em float usando o operador float. Neste exemplo, você divide por 1.0<degC> para os argumentos em printf porque printf espera quantidades sem dimensão.

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

A sessão de exemplo a seguir mostra as saídas e as entradas desse código.

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

Tipos primitivos que dão suporte a unidades de medida

Os seguintes tipos ou aliases de abreviação de tipo dão suporte a anotações de unidade de medida:

Alias do 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 exemplo, você pode anotar um inteiro sem sinal da seguinte maneira:

[<Measure>]
type days

let better_age = 3u<days>

A adição de tipos inteiros sem sinal a esse recurso está documentada em F# RFC FS-1091.

Unidades de medida predefinidas

Uma biblioteca de unidades está disponível no namespace FSharp.Data.UnitSystems.SI. Ele inclui unidades SI em sua forma de símbolo (como m para o medidor) no subnamespace UnitSymbols e em seu nome completo (como meter para o medidor) no subnamespace UnitNames.

Usando unidades genéricas

Você pode gravar funções genéricas que operam em dados com uma unidade de medida associada. Isso é feito especificando um tipo junto com uma unidade genérica como um parâmetro de tipo, conforme mostrado no exemplo de código a seguir.

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

Criando tipos de coleção com unidades genéricas

O código a seguir mostra como criar um tipo de agregação que consiste em valores de ponto flutuante individuais com unidades genéricas. Isso permite que um único tipo seja criado para funcionar com diversas unidades. Além disso, unidades genéricas preservam a segurança de tipos, garantindo que um tipo genérico que tenha um conjunto de unidades seja um tipo diferente do mesmo tipo genérico com um conjunto diferente de unidades. A base dessa técnica é que o atributo Measure pode ser aplicado ao 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 em Runtime

As unidades de medida são usadas para verificação de tipo estático. Quando os valores de ponto flutuante são compilados, as unidades de medida são eliminadas, portanto, as unidades são perdidas em tempo de execução. Portanto, qualquer tentativa de implementar a funcionalidade que depende da verificação das unidades em tempo de execução não é possível. Por exemplo, a implementação de uma função ToString para imprimir as unidades não é possível.

Conversões

Para converter um tipo com unidades (por exemplo, float<'u>) em um tipo sem unidades, é possível usar a função de conversão padrão. Por exemplo, você pode usar float para converter em um valor float que não tem unidades, conforme mostrado no código a seguir.

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

Para converter um valor sem unidade em um valor com unidades, multiplique por 1 ou um valor 1.0 anotado com as unidades apropriadas. No entanto, para gravar camadas de interoperabilidade, também há algumas funções explícitas que podem ser usadas para converter valores sem unidade em valores com unidades. Eles estão no módulo FSharp.Core.LanguagePrimitives. Por exemplo, para converter de um float sem unidade em um float<cm>, use FloatWithMeasure, conforme mostrado no código a seguir.

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

Confira também