Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement Request: Support decimal parameters/parsing #777

Open
JosephGray opened this issue May 13, 2024 · 0 comments
Open

Enhancement Request: Support decimal parameters/parsing #777

JosephGray opened this issue May 13, 2024 · 0 comments
Labels
feature proposal Features that are not approved yet

Comments

@JosephGray
Copy link

JosephGray commented May 13, 2024

Please add support to use the System.Decimal type (my own use case only requires decimal for parameters but it would be nice to support for parsing as well). To prevent breaking changes to existing code that utilizes xFunc, the support can be made optional so that default behavior is to continue to use double, and decimal-support must be explicitly opted-in for parsing.

Parser Example:

// assuming Parser ctor is given a new overload or an optional parameter:
var parser = new Parser(parseOptions: new ParseOptions { DefaultNumericType = NumberType.Decimal });

// or assuming the Parse method is given a new overload or an optional parameter:
var expression = parser.Parse("2.087991 * 3.14159". new ParseOptions { DefaultNumericType = NumberType.Decimal });

// maybe add a static property as well:
Parser.DefaultParseOptions = new ParseOptions { DefaultNumericType = NumberType.Decimal };

// if ParseOptions is not specified, use 'new ParseOptions { DefaultNumericType = NumberType.Double }` by default.
var parser = new Parser();
var expression = parser.Parse("2.087991 * 3.14159");

For parameters, handling can be modified to transparently convert between double/decimal and preserve precision when mathing between the two value types by subclassing (i.e., DecimalNumberValue and DoubleNumberValue with NumberValue refactored to an abstract base) or internally within NumberValue using a variation on the Maybe monad (similar to OneOf, except limited to decimal/double). Because existing code is limited exclusively to double, there should be little risk of breaking changes to existing consumers on update.

Barring explicit support for decimal types, maybe an extension point that the various operation nodes can support to defer evaluation to another object?

Example:

// supporting custom value types for multiplication

// marker/functional interfaces defined within xFunc
public interface ICanMath { }
public interface ICanMultiply : ICanMath
{
    bool TryMultiplyAsLeftOperand(object right, out ICanMath result);
    bool TryMultiplyAsRightOperand(object left, out ICanMath result);
}

// example custom value type
public record DecimalValue(decimal Value) : ICanMultiply
{
    public bool TryMultiplyAsLeftOperand(object right, out ICanMath result) =>
        TryMultiply(this, right, out result);

    public bool TryMultiplyAsRightOperand(object left, [NotNullWhen(true)] out ICanMath result) =>
        TryMultiply(left, this, out result);

    private static bool TryMultiply(object left, object right, [NotNullWhen(true)] out ICanMath result)
    {
        result =
            TryGetDecimal(left, out decimal? leftOperand) && TryGetDecimal(right, out decimal? rightOperand)
                ? new DecimalValue(leftOperand.Value * rightOperand.Value)
                : null;

        return result != null;
    }

    private static bool TryGetDecimal(object value, [NotNullWhen(true)] out decimal? dec)
    {
        dec =
            value switch
            {
                decimal d => d,
                double dbl => (decimal)dbl,
                NumberValue nv => (decimal)nv.Number,
                DecimalValue dv => dv.Value,
                _ => null
            };

        return dec.HasValue;
    }

    public static implicit operator ParameterValue(DecimalValue decimalValue) => new ParameterValue(decimalValue);
    public static implicit operator DecimalValue(decimal value) => new DecimalValue(value);
}

// within xFunc.Maths.Expression.Mul.Execute:
return (leftResult, rightResult) switch
{
    (NumberValue left, NumberValue right) => left * right,
    ...
    (ICanMultiply left, object right) when left.TryMultiplyAsLeftOperand(right, out ICanMath result) => result,
    (object left, ICanMultiply right) when right.TryMultiplyAsLeftOperand(left, out ICanMath result) => result,
    _ => throw ExecutionException.For(this),
};

// within ParemeterValue
public ParameterValue(ICanMath value)
    : this(value as object)     // update internal ctor to recognize ICanMath as valid type
{
}

// parse/execute:
var p = new Parser();
var expression = p.Parse("2 * myVar");
var parameters = new ExpressionParameters { ["myVar"] = new DecimalValue(3.14159M) };
var result = expression.Execute(parameters);  // 'result' is an instance of DecimalValue
@sys27 sys27 added feature proposal Features that are not approved yet labels May 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature proposal Features that are not approved yet
Projects
None yet
Development

No branches or pull requests

2 participants