Friday, December 2, 2011

F# ≥ C# (Units of Measure)


If you involves in any real world application, the number most likely has a special unit. Adding this unit not only make sure the code is more readable, but also decrease the chance to introduce a bug.

The unit is a compile time feature, so there is no performance hit by using unit.

The simple unit sample will be:
[ < Measure >  ]type litre
[ < Measure > ]type pint
let MeasureSample1() =
   let v1 = 2.
   let v2 = 1.
   let ratio =  1.0 / 1.76056338
   let pintToLitre pints =
       pints * ratio
the v1 and v2 are no longer simple float in the eyes of F# compiler. It is a special value with a unit. More complex samples will be something like the following code snippet.

The unit can not only decorate the simple type like float or int, but also decorate the complex type. The unit in the following code snippet is serving as a parameter to create new types: one is USD bank account and the other is CAD account. Can you easily find something like this in C#?

type AccountState =
   | Overdrawn
   | Silver
   | Gold
[ < Measure >  ] type USD
[ < Measure >  ] type CND
type Account <  [ < Measure > ]  'u > ( ) =
   let mutable balance = 0.0<_>
   member this.State
       with get() =
           match balance with
           | _ when balance <= 0.0<_> -> Overdrawn
           | _ when balance > 0.0<_> && balance < 10000.0<_> -> Silver
           | _ -> Gold
   member this.PayInterest() =
       let interest =
           match this.State with
               | Overdrawn -> 0.
               | Silver -> 0.01
               | Gold -> 0.02
       interest * balance
   member this.Deposit(x:float<_>) =  balance <- balance + x
   member this.Withdraw(x:float<_>) = balance <- balance - x
let MeasureSample4() =
   let account = Account < USD >()
   account.Deposit(LanguagePrimitives.FloatWithMeasure 10000.)
   printfn "us interest = %A" (account.PayInterest())
   account.Withdraw(LanguagePrimitives.FloatWithMeasure 20000.)
   printfn "us interest = %A" (account.PayInterest())
   let canadaAccount = Account < CND >()
   canadaAccount.Deposit(LanguagePrimitives.FloatWithMeasure 10000.)
   canadaAccount.Withdraw(LanguagePrimitives.FloatWithMeasure 500.)
   printfn "canadian interest = %A" (canadaAccount.PayInterest())

No comments: