Phillip Trelford's Array

POKE 36879,255

Runtime Units of Measure for F#

The F# compiler includes a Units of Measure feature which infers a measure type at compile time, which means you get measure type safety with uncompromised runtime performance.

Example of F#’s built-in Units of Measure feature (hover over shows inferred type):

metres per second

Sometimes you might also want to actually infer units of measure at runtime, say to display the inferred unit type at the UI. The following F# code prototype provides such inference.

Lets start by defining some metres and seconds unit types:

let m = UnitType.Create("m")
let s = UnitType.Create("s")

Now with the source below we can explore types in F# Interactive:

> 10.0 * m;;
val it : UnitValue = 10 m {Unit = Unit ("m",1);
                           Value = 10.0;}
> 10.0 * m / 5.0 * s;;
val it : UnitValue = 2 m s {Unit = CompositeUnit [Unit("m",1); Unit("s",1)];
                            Value = 2.0;}

Source code to F# runtime units of measure:

type UnitType =
    | Unit of string * int 
    | CompositeUnit of UnitType list     
    static member Create(s) = Unit(s,1)
    override this.ToString() =
        let exponent = function
            | Unit(_,n) -> n
            | CompositeUnit(_) ->                
                raise (new System.InvalidOperationException())
        let rec toString = function        
            | Unit(s,n) when n=0 -> ""
            | Unit(s,n) when n=1 -> s
            | Unit(s,n)          -> s + " ^ " + n.ToString()            
            | CompositeUnit(us) ->               
                let ps, ns = 
                    us |> List.partition (fun u -> exponent u >= 0)
                let join xs = 
                    let s = xs |> List.map toString |> List.toArray             
                    System.String.Join(" ",s)
                match ps,ns with 
                | ps, [] -> join ps
                | ps, ns ->
                    let ns = ns |> List.map UnitType.Reciprocal
                    join ps + " / " + join ns
        match this with
        | Unit(_,n) when n < 0 -> " / " + toString this
        | _ -> toString this    
    static member ( * ) (v:ValueType,u:UnitType) = UnitValue(v,u)    
    static member ( * ) (lhs:UnitType,rhs:UnitType) =
        let text = function
            | Unit(s,n) -> s
            | CompositeUnit(us) -> us.ToString()
        let normalize us u =
            let t = text u
            match us |> List.tryFind (fun x -> text x = t), u with
            | Some(Unit(s,n) as v), Unit(_,n') ->
                us |> List.map (fun x -> if x = v then Unit(s,n+n') else x)                 
            | Some(_), _ -> raise (new System.NotImplementedException())
            | None, _ -> us@[u]
        let normalize' us us' =
            us' |> List.fold (fun (acc) x -> normalize acc x) us
        match lhs,rhs with
        | Unit(u1,p1), Unit(u2,p2) when u1 = u2 ->
            Unit(u1,p1+p2)        
        | Unit(u1,p1), Unit(u2,p2) ->            
            CompositeUnit([lhs;rhs])
        | CompositeUnit(us), Unit(_,_) ->
            CompositeUnit(normalize us rhs)
        | Unit(_,_), CompositeUnit(us) ->
            CompositeUnit(normalize' [lhs]  us)
        | CompositeUnit(us), CompositeUnit(us') ->
            CompositeUnit(normalize' us us')
        | _,_ -> raise (new System.NotImplementedException())
    static member Reciprocal x =
        let rec reciprocal = function
            | Unit(s,n) -> Unit(s,-n)
            | CompositeUnit(us) -> CompositeUnit(us |> List.map reciprocal)
        reciprocal x
    static member ( / ) (lhs:UnitType,rhs:UnitType) =        
        lhs * (UnitType.Reciprocal rhs)
    static member ( + ) (lhs:UnitType,rhs:UnitType) =       
        if lhs = rhs then lhs                
        else raise (new System.InvalidOperationException())
and ValueType = float
and UnitValue(v:ValueType,u:UnitType) = 
    member this.Value = v 
    member this.Unit = u
    override this.ToString() = sprintf "%O %O" v u
    static member (+) (lhs:UnitValue,rhs:UnitValue) =
        UnitValue(lhs.Value+rhs.Value, lhs.Unit+rhs.Unit)         
    static member (*) (lhs:UnitValue,rhs:UnitValue) =                    
        UnitValue(lhs.Value*rhs.Value,lhs.Unit*rhs.Unit)                
    static member (*) (lhs:UnitValue,rhs:ValueType) =        
        UnitValue(lhs.Value*rhs,lhs.Unit)      
    static member (*) (v:UnitValue,u:UnitType) = 
        UnitValue(v.Value,v.Unit*u)  
    static member (/) (lhs:UnitValue,rhs:UnitValue) =                    
        UnitValue(lhs.Value/rhs.Value,lhs.Unit/rhs.Unit)
    static member (/) (lhs:UnitValue,rhs:ValueType) =
        UnitValue(lhs.Value/rhs,lhs.Unit)  
    static member (/) (v:UnitValue,u:UnitType) = 
        UnitValue(v.Value,v.Unit/u)


Implementation details:

Unit (UnitType) computations are separate from value (UnitValue) computations. A single unit types (say metres) just has a name and a power (default of 1):

let metres = Unit(name = “metres”, power = 1)

To multiply by the same unit type, simply add the powers:

(2.0 * metres) * (3.0 metres) = 6.0 metres ^ 2

To handle composite unit types, when multiplying 2 unit values, first try to find a matching unit type in the existing list, if successful add the powers, otherwise add the new type:

2.0 * metres * seconds = 2 .0 metres (per) second

To divide simply multiply by the reciprocal:

(2.0 * metres * seconds) / (1.0 * seconds) = 2.0 metres per second * 1.0 seconds ^ -1

Resources:

Pingbacks and trackbacks (6)+

Comments are closed