Skip to content

Commit

Permalink
Finish genericity and keep the tests at decimal so far.
Browse files Browse the repository at this point in the history
  • Loading branch information
FreeApophis committed Aug 25, 2023
1 parent 5f4c5df commit c758d0a
Show file tree
Hide file tree
Showing 29 changed files with 362 additions and 308 deletions.
8 changes: 4 additions & 4 deletions Funcky.Money.SourceGenerator/Iso4217RecordGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private static string GenerateMoneyClass(IEnumerable<Iso4217Record> records)
=> $"using Funcky.Monads;\n" +
$"namespace {RootNamespace}\n" +
$"{{\n" +
$"{Indent}public partial record Money\n" +
$"{Indent}public partial record Money<TUnderlyingType>\n" +
$"{Indent}{{\n" +
$"{GenerateMoneyFactoryMethods(records)}" +
$"{Indent}}}\n" +
Expand All @@ -119,9 +119,9 @@ private static string CreateCurrencyFactory(Iso4217Record record)
{
var identifier = Identifier(record.AlphabeticCurrencyCode);

return $"{Indent}{Indent}/// <summary>Creates a new <see cref=\"Money\" /> instance using the <see cref=\"Currency.{identifier}\" /> currency.</summary>\n" +
$"{Indent}{Indent}public static Money {identifier}(decimal amount)\n" +
$"{Indent}{Indent} => new(amount, MoneyEvaluationContext.Builder.Default.WithTargetCurrency(Currency.{identifier}).Build());";
return $"{Indent}{Indent}/// <summary>Creates a new <see cref=\"Money{{TUnderlyingType}}\" /> instance using the <see cref=\"Currency.{identifier}\" /> currency.</summary>\n" +
$"{Indent}{Indent}public static Money<TUnderlyingType> {identifier}(TUnderlyingType amount)\n" +
$"{Indent}{Indent} => new(amount, MoneyEvaluationContext<TUnderlyingType>.Builder.Default.WithTargetCurrency(Currency.{identifier}).Build());";
}

private static IEnumerable<Iso4217Record> ReadIso4217RecordsFromAdditionalFiles(
Expand Down
8 changes: 4 additions & 4 deletions Funcky.Money.Test/MoneyArbitraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ internal class MoneyArbitraries
public static Arbitrary<Currency> ArbitraryCurrency()
=> Arb.From(Gen.Elements<Currency>(Currency.AllCurrencies));

public static Arbitrary<Money> ArbitraryMoney()
public static Arbitrary<Money<decimal>> ArbitraryMoney()
=> GenerateMoney().ToArbitrary();

public static Arbitrary<SwissMoney> ArbitrarySwissMoney()
=> GenerateSwissFranc().ToArbitrary();

private static Gen<Money> GenerateMoney()
private static Gen<Money<decimal>> GenerateMoney()
=> from currency in Arb.Generate<Currency>()
from amount in Arb.Generate<int>()
select new Money(Power<decimal>.OfATenth(currency.MinorUnitDigits) * amount, currency);
select new Money<decimal>(Power<decimal>.OfATenth(currency.MinorUnitDigits) * amount, currency);

private static Gen<SwissMoney> GenerateSwissFranc()
=> from amount in Arb.Generate<int>()
select new SwissMoney(Money.CHF(SwissMoney.SmallestCoin * amount));
select new SwissMoney(Money<decimal>.CHF(SwissMoney.SmallestCoin * amount));
}
190 changes: 95 additions & 95 deletions Funcky.Money.Test/MoneyTest.cs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Funcky.Money.Test/SwissMoney.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Funcky.Test;

public record SwissMoney(Money Get)
public record SwissMoney(Money<decimal> Get)
{
public const decimal SmallestCoin = 0.05m;
}
60 changes: 31 additions & 29 deletions Funcky.Money/Distribution/DefaultDistributionStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,85 +1,87 @@
using System.Numerics;
using Funcky.Extensions;
using Funcky.Monads;

namespace Funcky;

internal class DefaultDistributionStrategy : IDistributionStrategy
internal class DefaultDistributionStrategy<TUnderlyingType> : IDistributionStrategy<TUnderlyingType>
where TUnderlyingType : IFloatingPoint<TUnderlyingType>
{
private readonly Option<MoneyEvaluationContext> _context;
private readonly Option<MoneyEvaluationContext<TUnderlyingType>> _context;

public DefaultDistributionStrategy(Option<MoneyEvaluationContext> context)
public DefaultDistributionStrategy(Option<MoneyEvaluationContext<TUnderlyingType>> context)
{
_context = context;
}

public Money Distribute(MoneyDistributionPart part, Money total)
public Money<TUnderlyingType> Distribute(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> total)
=> IsDistributable(part, total)
? total with { Amount = SliceAmount(part, total), Currency = total.Currency }
: throw new ImpossibleDistributionException($"It is impossible to distribute {ToDistribute(part, total)} in sizes of {Precision(part.Distribution, total)} with the current Rounding strategy: {RoundingStrategy(total)}.");

private bool IsDistributable(MoneyDistributionPart part, Money money)
private bool IsDistributable(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money)
=> RoundingStrategy(money).IsSameAfterRounding(Precision(part.Distribution, money))
&& RoundingStrategy(money).IsSameAfterRounding(ToDistribute(part, money));

private decimal SliceAmount(MoneyDistributionPart part, Money money)
private TUnderlyingType SliceAmount(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money)
=> Slice(part.Distribution, part.Index, money) + DistributeRest(part, money);

private decimal DistributeRest(MoneyDistributionPart part, Money money)
private TUnderlyingType DistributeRest(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money)
=> part.Index switch
{
_ when AtLeastOneDistributionUnitLeft(part, money) => SignedPrecision(part.Distribution, money),
_ when BetweenZeroToOneDistributionUnitLeft(part, money) => ToDistribute(part, money) - AlreadyDistributed(part, money),
_ => 0.0m,
_ => TUnderlyingType.Zero,
};

private bool AtLeastOneDistributionUnitLeft(MoneyDistributionPart part, Money money)
=> Precision(part.Distribution, money) * (part.Index + 1) < Math.Abs(ToDistribute(part, money));
private bool AtLeastOneDistributionUnitLeft(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money)
=> Precision(part.Distribution, money) * TUnderlyingType.CreateChecked(part.Index + 1) < TUnderlyingType.Abs(ToDistribute(part, money));

private bool BetweenZeroToOneDistributionUnitLeft(MoneyDistributionPart part, Money money)
=> Precision(part.Distribution, money) * part.Index < Math.Abs(ToDistribute(part, money));
private bool BetweenZeroToOneDistributionUnitLeft(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money)
=> Precision(part.Distribution, money) * TUnderlyingType.CreateChecked(part.Index) < TUnderlyingType.Abs(ToDistribute(part, money));

private decimal AlreadyDistributed(MoneyDistributionPart part, Money money)
=> SignedPrecision(part.Distribution, money) * part.Index;
private TUnderlyingType AlreadyDistributed(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money)
=> SignedPrecision(part.Distribution, money) * TUnderlyingType.CreateChecked(part.Index);

private IRoundingStrategy<decimal> RoundingStrategy(Money money)
private IRoundingStrategy<TUnderlyingType> RoundingStrategy(Money<TUnderlyingType> money)
=> _context.Match(
some: c => c.RoundingStrategy,
none: money.RoundingStrategy);

private decimal ToDistribute(MoneyDistributionPart part, Money money)
private TUnderlyingType ToDistribute(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money)
=> money.Amount - DistributedTotal(part, money);

private decimal DistributedTotal(MoneyDistributionPart part, Money money)
private TUnderlyingType DistributedTotal(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> money)
=> part
.Distribution
.Factors
.WithIndex()
.Sum(f => Slice(part.Distribution, f.Index, money));
.Aggregate(TUnderlyingType.Zero, (sum, value) => sum + Slice(part.Distribution, value.Index, money));

private decimal Slice(MoneyDistribution distribution, int index, Money money)
private TUnderlyingType Slice(MoneyDistribution<TUnderlyingType> distribution, int index, Money<TUnderlyingType> money)
=> Truncate(ExactSlice(distribution, index, money), Precision(distribution, money));

private static decimal ExactSlice(MoneyDistribution distribution, int index, Money money)
=> money.Amount / DistributionTotal(distribution) * distribution.Factors[index];
private static TUnderlyingType ExactSlice(MoneyDistribution<TUnderlyingType> distribution, int index, Money<TUnderlyingType> money)
=> money.Amount / TUnderlyingType.CreateChecked(DistributionTotal(distribution)) * TUnderlyingType.CreateChecked(distribution.Factors[index]);

private decimal SignedPrecision(MoneyDistribution distribution, Money money)
=> Precision(distribution, money).CopySign(money.Amount);
private TUnderlyingType SignedPrecision(MoneyDistribution<TUnderlyingType> distribution, Money<TUnderlyingType> money)
=> TUnderlyingType.CopySign(Precision(distribution, money), money.Amount);

// Order of evaluation: Distribution > Context Distribution > Context Currency > Money Currency
private decimal Precision(MoneyDistribution distribution, Money money)
private TUnderlyingType Precision(MoneyDistribution<TUnderlyingType> distribution, Money<TUnderlyingType> money)
=> distribution
.Precision
.OrElse(_context.AndThen(c => c.DistributionUnit))
.GetOrElse(Power<decimal>.OfATenth(MinorUnitDigits(money)));
.GetOrElse(Power<TUnderlyingType>.OfATenth(MinorUnitDigits(money)));

private int MinorUnitDigits(Money money)
private int MinorUnitDigits(Money<TUnderlyingType> money)
=> _context.Match(
none: money.Currency.MinorUnitDigits,
some: c => c.TargetCurrency.MinorUnitDigits);

private static decimal Truncate(decimal amount, decimal precision)
=> decimal.Truncate(amount / precision) * precision;
private static TUnderlyingType Truncate(TUnderlyingType amount, TUnderlyingType precision)
=> TUnderlyingType.Truncate(amount / precision) * precision;

private static int DistributionTotal(MoneyDistribution distribution)
private static int DistributionTotal(MoneyDistribution<TUnderlyingType> distribution)
=> distribution.Factors.Sum();
}
7 changes: 5 additions & 2 deletions Funcky.Money/Distribution/IDistributionStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System.Numerics;

namespace Funcky;

internal interface IDistributionStrategy
internal interface IDistributionStrategy<TUnderlyingType>
where TUnderlyingType : IFloatingPoint<TUnderlyingType>
{
Money Distribute(MoneyDistributionPart part, Money total);
Money<TUnderlyingType> Distribute(MoneyDistributionPart<TUnderlyingType> part, Money<TUnderlyingType> total);
}
27 changes: 14 additions & 13 deletions Funcky.Money/ExpressionNodes/IMoneyExpression.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
using System.Numerics;

namespace Funcky;

public interface IMoneyExpression
public interface IMoneyExpression<TUnderlyingType>
where TUnderlyingType : IFloatingPoint<TUnderlyingType>
{
#if DEFAULT_INTERFACE_IMPLEMENTATION_SUPPORTED
public static IMoneyExpression operator *(IMoneyExpression multiplicand, decimal multiplier)
public static IMoneyExpression<TUnderlyingType> operator *(IMoneyExpression<TUnderlyingType> multiplicand, TUnderlyingType multiplier)
=> multiplicand.Multiply(multiplier);

public static IMoneyExpression operator *(decimal multiplier, IMoneyExpression multiplicand)
public static IMoneyExpression<TUnderlyingType> operator *(TUnderlyingType multiplier, IMoneyExpression<TUnderlyingType> multiplicand)
=> multiplicand.Multiply(multiplier);

public static IMoneyExpression operator /(IMoneyExpression dividend, decimal divisor)
public static IMoneyExpression<TUnderlyingType> operator /(IMoneyExpression<TUnderlyingType> dividend, TUnderlyingType divisor)
=> dividend.Divide(divisor);

public static decimal operator /(IMoneyExpression dividend, IMoneyExpression divisor)
public static TUnderlyingType operator /(IMoneyExpression<TUnderlyingType> dividend, IMoneyExpression<TUnderlyingType> divisor)
=> dividend.Divide(divisor);

public static IMoneyExpression operator +(IMoneyExpression augend, IMoneyExpression addend)
public static IMoneyExpression<TUnderlyingType> operator +(IMoneyExpression<TUnderlyingType> augend, IMoneyExpression<TUnderlyingType> addend)
=> augend.Add(addend);

public static IMoneyExpression operator +(IMoneyExpression moneyExpression)
public static IMoneyExpression<TUnderlyingType> operator +(IMoneyExpression<TUnderlyingType> moneyExpression)
=> moneyExpression;

public static IMoneyExpression operator -(IMoneyExpression minuend, IMoneyExpression subtrahend)
public static IMoneyExpression<TUnderlyingType> operator -(IMoneyExpression<TUnderlyingType> minuend, IMoneyExpression<TUnderlyingType> subtrahend)
=> minuend.Subtract(subtrahend);

public static IMoneyExpression operator -(IMoneyExpression moneyExpression)
=> moneyExpression.Multiply(-1);
#endif
public static IMoneyExpression<TUnderlyingType> operator -(IMoneyExpression<TUnderlyingType> moneyExpression)
=> moneyExpression.Multiply(TUnderlyingType.NegativeOne);

internal TState Accept<TState>(IMoneyExpressionVisitor<TState> visitor)
internal TState Accept<TState>(IMoneyExpressionVisitor<TUnderlyingType, TState> visitor)
where TState : notnull;
}
14 changes: 8 additions & 6 deletions Funcky.Money/ExpressionNodes/MoneyDistribution.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System.Collections;
using System.Numerics;
using Funcky.Extensions;
using Funcky.Monads;

namespace Funcky;

internal sealed class MoneyDistribution : IEnumerable<IMoneyExpression>
internal sealed class MoneyDistribution<TUnderlyingType> : IEnumerable<IMoneyExpression<TUnderlyingType>>
where TUnderlyingType : IFloatingPoint<TUnderlyingType>
{
public MoneyDistribution(IMoneyExpression moneyExpression, IEnumerable<int> factors, Option<decimal> precision)
public MoneyDistribution(IMoneyExpression<TUnderlyingType> moneyExpression, IEnumerable<int> factors, Option<TUnderlyingType> precision)
{
Expression = moneyExpression;
Factors = factors.ToList();
Expand All @@ -18,16 +20,16 @@ public MoneyDistribution(IMoneyExpression moneyExpression, IEnumerable<int> fact
}
}

public IMoneyExpression Expression { get; }
public IMoneyExpression<TUnderlyingType> Expression { get; }

public List<int> Factors { get; }

public Option<decimal> Precision { get; }
public Option<TUnderlyingType> Precision { get; }

public IEnumerator<IMoneyExpression> GetEnumerator()
public IEnumerator<IMoneyExpression<TUnderlyingType>> GetEnumerator()
=> Factors
.WithIndex()
.Select(f => (IMoneyExpression)new MoneyDistributionPart(this, f.Index))
.Select(f => (IMoneyExpression<TUnderlyingType>)new MoneyDistributionPart<TUnderlyingType>(this, f.Index))
.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator()
Expand Down
11 changes: 7 additions & 4 deletions Funcky.Money/ExpressionNodes/MoneyDistributionPart.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
using System.Numerics;

namespace Funcky;

internal sealed class MoneyDistributionPart : IMoneyExpression
internal sealed class MoneyDistributionPart<TUnderlyingType> : IMoneyExpression<TUnderlyingType>
where TUnderlyingType : IFloatingPoint<TUnderlyingType>
{
public MoneyDistributionPart(MoneyDistribution distribution, int index)
public MoneyDistributionPart(MoneyDistribution<TUnderlyingType> distribution, int index)
{
Distribution = distribution;
Index = index;
}

public MoneyDistribution Distribution { get; }
public MoneyDistribution<TUnderlyingType> Distribution { get; }

public int Index { get; }

TState IMoneyExpression.Accept<TState>(IMoneyExpressionVisitor<TState> visitor)
TState IMoneyExpression<TUnderlyingType>.Accept<TState>(IMoneyExpressionVisitor<TUnderlyingType, TState> visitor)
=> visitor.Visit(this);
}
13 changes: 8 additions & 5 deletions Funcky.Money/ExpressionNodes/MoneyProduct.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
using System.Numerics;

namespace Funcky;

internal sealed record MoneyProduct : IMoneyExpression
internal sealed record MoneyProduct<TUnderlyingType> : IMoneyExpression<TUnderlyingType>
where TUnderlyingType : IFloatingPoint<TUnderlyingType>
{
public MoneyProduct(IMoneyExpression moneyExpression, decimal factor)
public MoneyProduct(IMoneyExpression<TUnderlyingType> moneyExpression, TUnderlyingType factor)
{
Expression = moneyExpression;
Factor = factor;
}

public IMoneyExpression Expression { get; }
public IMoneyExpression<TUnderlyingType> Expression { get; }

public decimal Factor { get; }
public TUnderlyingType Factor { get; }

TState IMoneyExpression.Accept<TState>(IMoneyExpressionVisitor<TState> visitor)
TState IMoneyExpression<TUnderlyingType>.Accept<TState>(IMoneyExpressionVisitor<TUnderlyingType, TState> visitor)
=> visitor.Visit(this);
}
13 changes: 8 additions & 5 deletions Funcky.Money/ExpressionNodes/MoneySum.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
using System.Numerics;

namespace Funcky;

internal sealed record MoneySum : IMoneyExpression
internal sealed record MoneySum<TUnderlyingType> : IMoneyExpression<TUnderlyingType>
where TUnderlyingType : IFloatingPoint<TUnderlyingType>
{
public MoneySum(IMoneyExpression leftMoneyExpression, IMoneyExpression rightMoneyExpression)
public MoneySum(IMoneyExpression<TUnderlyingType> leftMoneyExpression, IMoneyExpression<TUnderlyingType> rightMoneyExpression)
{
Left = leftMoneyExpression;
Right = rightMoneyExpression;
}

public IMoneyExpression Left { get; }
public IMoneyExpression<TUnderlyingType> Left { get; }

public IMoneyExpression Right { get; }
public IMoneyExpression<TUnderlyingType> Right { get; }

TState IMoneyExpression.Accept<TState>(IMoneyExpressionVisitor<TState> visitor)
TState IMoneyExpression<TUnderlyingType>.Accept<TState>(IMoneyExpressionVisitor<TUnderlyingType, TState> visitor)
=> visitor.Visit(this);
}
9 changes: 6 additions & 3 deletions Funcky.Money/Extensions/MoneyDistributionExtension.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using System.Numerics;
using Funcky.Monads;

namespace Funcky;

public static class MoneyDistributionExtension
{
public static IEnumerable<IMoneyExpression> Distribute(this IMoneyExpression moneyExpression, int numberOfSlices, Option<decimal> precision = default)
public static IEnumerable<IMoneyExpression<TUnderlyingType>> Distribute<TUnderlyingType>(this IMoneyExpression<TUnderlyingType> moneyExpression, int numberOfSlices, Option<TUnderlyingType> precision = default)
where TUnderlyingType : IFloatingPoint<TUnderlyingType>
=> moneyExpression.Distribute(Enumerable.Repeat(element: 1, count: numberOfSlices), precision);

public static IEnumerable<IMoneyExpression> Distribute(this IMoneyExpression moneyExpression, IEnumerable<int> factors, Option<decimal> precision = default)
=> new MoneyDistribution(moneyExpression, factors, precision);
public static IEnumerable<IMoneyExpression<TUnderlyingType>> Distribute<TUnderlyingType>(this IMoneyExpression<TUnderlyingType> moneyExpression, IEnumerable<int> factors, Option<TUnderlyingType> precision = default)
where TUnderlyingType : IFloatingPoint<TUnderlyingType>
=> new MoneyDistribution<TUnderlyingType>(moneyExpression, factors, precision);
}
Loading

0 comments on commit c758d0a

Please sign in to comment.