-
-
Save adelarsq/e6cfb9c968a0ad3972450836f9040aa0 to your computer and use it in GitHub Desktop.
Monetary value modeling in F#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Domain | |
open System | |
open System.Runtime.CompilerServices | |
open System.Globalization | |
[<AutoOpen>] | |
module rec Money = | |
[<Struct;IsReadOnly>] | |
type Money = private Money of decimal * Currency with | |
member this.Times (n : int) = | |
let (Money (m, c)) = this | |
Money (m * decimal n, c) | |
static member TryAdd (Money (a, c1)) (Money (b, c2)) = | |
if c1 = c2 then Money (a + b, c1) |> Some else None | |
static member TrySubstract (Money (a, c1)) (Money (b, c2)) = | |
if c1 = c2 then Money (a - b, c1) |> Some else None | |
static member Negate (Money (a, c)) = Money (-a, c) | |
static member inline (+)(a: Money, b: Money) = | |
match Money.TryAdd a b with | |
| Some c -> c | |
| None -> raise (InvalidOperationException("Unable to add values with different currencies")) | |
static member inline (-)(a: Money, b:Money) = | |
match Money.TrySubstract a b with | |
| Some c -> c | |
| None -> raise (InvalidOperationException("Unable to subtract values with different currencies")) | |
static member inline (~-)(m: Money) = Money.Negate m | |
static member inline (*)(m: Money, n: int) = m.Times n | |
static member inline (*)(n: int, m: Money) = m * n | |
override this.ToString() = | |
let (Money (amount, (Currency (code, decimalPlaces)))) = this | |
let fmt = | |
match decimalPlaces with | |
| Zero -> "0" | |
| One -> "0.0" | |
| Two -> "0.00" | |
| Three -> "0.000" | |
| Four -> "0.0000" | |
sprintf "%s %A" (amount.ToString(fmt, CultureInfo.InvariantCulture)) code | |
let zero currency = Money (0m, currency) | |
let fractionalUnit (Currency (code, decimalPlaces)) = Money (pown 10m -(DecimalPlaces.value decimalPlaces), (Currency (code, decimalPlaces))) | |
let private round' midpointRounding (Currency (code, decimalPlaces)) amount = | |
Money (Decimal.Round(amount, DecimalPlaces.value decimalPlaces, midpointRounding), Currency (code, decimalPlaces)) | |
let round (Currency (code, decimalPlaces)) amount = | |
Money (Decimal.Round(amount, DecimalPlaces.value decimalPlaces), Currency (code, decimalPlaces)) | |
let floor = round' MidpointRounding.ToNegativeInfinity | |
let ceiling = round' MidpointRounding.ToPositiveInfinity | |
let truncate = round' MidpointRounding.ToZero | |
let amount (Money (a, _)) = a | |
let currency (Money (_, c)) = c | |
[<Struct;IsReadOnly>] | |
type NonNegative = private NonNegative of Money with | |
override this.ToString() = | |
let (NonNegative m) = this | |
m.ToString() | |
static member FromPositive(m: Money.Positive) = m |> Positive.value |> Money.NonNegative | |
module NonNegative = | |
let tryCreate (m : Money) = | |
match m with | |
| (Money (a, _)) when a >= 0m -> m |> NonNegative |> Some | |
| _ -> None | |
let create m = | |
match tryCreate m with | |
| Some pm -> pm | |
| None -> raise (ArgumentOutOfRangeException (nameof(m))) | |
let value (NonNegative m) = m | |
[<Struct;IsReadOnly>] | |
type Positive = private Positive of Money with | |
override this.ToString() = | |
let (Positive m) = this | |
m.ToString() | |
module Positive = | |
let tryCreate (m : Money) = | |
match m with | |
| (Money (a, _)) when a > 0m -> m |> Positive |> Some | |
| _ -> None | |
let create m = | |
match tryCreate m with | |
| Some pm -> pm | |
| None -> raise (ArgumentOutOfRangeException (nameof(m))) | |
let value (Positive m) = m |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment