Moyen d'associer un type numérique à une unité de mesure
Durée : sakasecond
Masse : kg
Longueur : makametre
...
Les unités sont vérifiées à la compilation
Empêche d'ajouter des 🥦 à des 🥕 → code + sûr
Permet de les combiner : Vitesse = Distance / Durée → m/s
Déclaration
Syntaxe basée sur attribut [<Measure>]
// 👉 Nouvelles unités "from scratch"
[<Measure>] type kilogram
[<Measure>] type metre
[<Measure>] type second
// 👉 Alias d'unités existantes
[<Measure>] type kg = kilogram
[<Measure>] type m = metre
[<Measure>] type s = second
// 👉 Combinaison d'unités existantes
[<Measure>] type Hz = / s
[<Measure>] type N = kg m / s^2
SI
Les unités du Système International sont prédéfinies dans les namespaces :
// Unité définie en annotant le nombre
let distance = 1.0<m> // val distance : float<m> = 1.0
let time = 2.0<s> // val time : float<s> = 2.0
// Unité combinée, inférée
let speed = distance / time // val speed : float<m/s> = 0.5
// Unité combinée, définie par annotation
let [<Literal>] G = 9.806<m/s^2> // val G : float<m/s ^ 2> = 9.806
// Comparaison
let sameFrequency = (1<Hz> = 1</s>) // ✅ true
let ko1 = (distance = 1.0) // ❌ Error FS0001: Incompatibilité de type.
// 💥 Attente de 'float<m>' mais obtention de 'float'
let ko2 = (distance = 1<m>) // 💥 Attente de 'float<m>' mais obtention de 'int<m>'
let ko3 = (distance = time) // 💥 Attente de 'float<m>' mais obtention de 'float<s>'
Symbole
💡 Astuce : utilisation des doubles back ticks
[<Measure>] type ``Ω``
[<Measure>] type ``°C``
[<Measure>] type ``°F``
let waterFreezingAt = 0.0<``°C``>
// val waterFreezingAt : float<°C> = 0.0
let waterBoilingAt = 100.0<``°C``>
// val waterBoilingAt : float<°C> = 100.0
Conversion
Facteur multiplicatif avec une unité <target/source>
Fonction de conversion utilisant ce facteur
[<Measure>] type m
[<Measure>] type cm
[<Measure>] type km
module Distance =
let toCentimeter (x: float<m>) = // (x: float<m>) -> float<cm>
x * 100.0<cm/m>
let toKilometer (x: float<m>) = // (x: float<m>) -> float<km>
x / 1000.0<m/km>
let a = Distance.toCentimeter 1.0<m> // val a : float<cm> = 100.0
let b = Distance.toKilometer 500.0<m> // val b : float<km> = 0.5
Exemple 2 : degré Celsius (°C) → degré Fahrenheit (°F)
[<Measure>] type ``°C``
[<Measure>] type ``°F``
module Temperature =
let toFahrenheit ( x: float<``°C``> ) = // (x: float<°C>) -> float<°F>
9.0<``°F``> / 5.0<``°C``> * x + 32.0<``°F``>
let waterFreezingAt = Temperature.toFahrenheit 0.0<``°C``>
// val waterFreezingAt : float<°F> = 32.0
let waterBoilingAt = Temperature.toFahrenheit 100.0<``°C``>
// val waterBoilingAt : float<°F> = 212.0
Ajouter/supprimer
Ajouter une unité à un nombre nu :
✅ number * 1.0<target>
Supprimer l'unité d'un nombre number : float<source> :
✅ number / 1.0<source>
✅ float number
Créer une liste de nombres avec unité :
✅ [1<m>; 2<m>; 3<m>]
❌ [1<m>..3<m>](un range nécessite des nombres nus)
✅ [ for i in [1..3] -> i * 1<m> ]
Effacées au runtime !
Les unités de mesure sont propres au compilateur F♯. → Elles ne sont pas compilées en .NET
Type avec unité générique
Besoin de distinguer d'un type générique classique → Annoter l'unité générique avec [<Measure>]
type Point<[<Measure>] 'u, 'data> =
{ X: float<'u>; Y: float<'u>; Data: 'data }
let point = { X = 10.0<m>; Y = 2.0<m>; Data = "abc" }
// val point : Point<m, string> = { X = 10.0; Y = 2.0; Data = "abc" }
Unité pour primitive non numérique
open System
#r "nuget: FSharp.UMX"
open FSharp.UMX
[<Measure>] type ClientId
[<Measure>] type OrderId
type Order = { Id: Guid<OrderId>; ClientId: string<ClientId> }
let order = { Id = % Guid.NewGuid(); ClientId = % "RDE" }