Skip to content

Instantly share code, notes, and snippets.

@williamcotton
Last active October 10, 2024 01:26
Show Gist options
  • Save williamcotton/b67ff641eeec767313171583d7873b7e to your computer and use it in GitHub Desktop.
Save williamcotton/b67ff641eeec767313171583d7873b7e to your computer and use it in GitHub Desktop.
F# Money Types
open System
// Define units of measurement
[<Measure>] type USD
[<Measure>] type BTC
[<Measure>] type percent
let btcUsdRate = 62500m<USD/BTC>
// Money type alias
type Money<[<Measure>] 'currency> = decimal<'currency>
type Percentage = decimal<percent>
// Money module
module Money =
let increaseBy<[<Measure>] 'currency> (p: Percentage) (m: Money<'currency>) : Money<'currency> =
m * (1m + decimal p / 100m)
let decreaseBy<[<Measure>] 'currency> (p: Percentage) (m: Money<'currency>) : Money<'currency> =
m * (1m - decimal p / 100m)
let allocate<[<Measure>] 'currency> (m: Money<'currency>) (count: int) : Money<'currency> list =
let baseAllocation = m / decimal count
let remainder = decimal m % decimal count
[1..count]
|> List.map (fun i ->
if i <= int remainder
then baseAllocation + LanguagePrimitives.DecimalWithMeasure<'currency> 1m
else baseAllocation)
let allocateByRatios<[<Measure>] 'currency> (m: Money<'currency>) (ratios: Percentage list) : Money<'currency> list =
let totalRatio = ratios |> List.sumBy (fun r -> decimal r / 100m)
if Math.Abs(1m - totalRatio) > 0.0001m then
failwith "Sum of ratios must be 100%"
let allocations =
ratios
|> List.map (fun r -> m * (decimal r / 100m))
let totalAllocated = allocations |> List.sum
let remainder = m - totalAllocated
allocations
|> List.mapi (fun i a ->
if i = 0 then a + remainder else a)
// USD examples
let usdPrice = 100m<USD>
let usdShipping = 5m<USD>
let usdSubtotal = usdPrice + usdShipping
let discount = 10m<percent>
let usdTotal = Money.decreaseBy discount usdSubtotal
let ratios = [60m<percent>; 40m<percent>]
let usdEqualAllocation = Money.allocate usdTotal 2
let usdRatioAllocation = Money.allocateByRatios usdTotal ratios
// Print USD results
printfn "USD Price: %A" usdPrice
printfn "USD Shipping: %A" usdShipping
printfn "USD Subtotal: %A" usdSubtotal
printfn "Discount: %A%%" discount
printfn "USD Total: %A" usdTotal
printfn "USD Equal allocation: %A" usdEqualAllocation
printfn "USD Ratio allocation: %A" usdRatioAllocation
// BTC examples
let btcPrice = 0.01607580m<BTC>
let transactionFee = 1.25m<percent>
let btcTotal = Money.increaseBy transactionFee btcPrice
let btcInstallments = Money.allocate btcTotal 3
// Print BTC results
printfn "\nBTC Price: %A" btcPrice
printfn "BTC Total with fee: %A" btcTotal
printfn "BTC Installments: %A" btcInstallments
// Demonstration of type safety
let invalidAdd = usdPrice + btcPrice // This will cause a compile-time error
let exchangedUsd = btcPrice * btcUsdRate // This is valid
let validBtcAllocate = Money.allocate btcTotal 2 // This is valid and works with BTC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment