Last active
July 15, 2022 14:25
-
-
Save nikoloz-pachuashvili/37cd15f5b7957c498ce78e093179aa30 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