Skip to content

Creating compiled lambda expression math class for future use - #698

Merged
angularsen merged 7 commits into
angularsen:masterfrom
tmilnthorp:LambdaMath
May 22, 2020
Merged

angularsen merged 7 commits into
angularsen:masterfrom
tmilnthorp:LambdaMath

Conversation

@tmilnthorp

Copy link
Copy Markdown
Collaborator

For potential future use for generic quantity structs

@tmilnthorp

Copy link
Copy Markdown
Collaborator Author

#695 @ZacharyPatten

@codecov-io

codecov-io commented Aug 28, 2019 •

Copy link
Copy Markdown

Codecov Report

Merging #698 into master will increase coverage by 0.66%.
The diff coverage is 85.1%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #698      +/-   ##
==========================================
+ Coverage   58.34%   59.01%   +0.66%     
==========================================
  Files         165      172       +7     
  Lines       37450    39616    +2166     
==========================================
+ Hits        21852    23379    +1527     
- Misses      15598    16237     +639
Impacted Files Coverage Δ
UnitsNet/CompiledLambdas.cs 85.1% <85.1%> (ø)
UnitsNet/GeneratedCode/UnitConverter.g.cs 100% <0%> (ø) ⬆️
...tsNet/GeneratedCode/Quantities/FuelEfficiency.g.cs 43.72% <0%> (ø)
...Net/CustomCode/Quantities/ElectricCurrent.extra.cs 100% <0%> (ø)
.../CustomCode/Quantities/ElectricResistance.extra.cs 100% <0%> (ø)
...CustomCode/Quantities/AreaMomentOfInertia.extra.cs 100% <0%> (ø)
...sNet/GeneratedCode/Quantities/TorquePerLength.g.cs 59.9% <0%> (ø)
UnitsNet/CustomCode/Wrappers/ReferencePressure.cs 96.36% <0%> (ø)
UnitsNet/GeneratedCode/Quantities/Energy.g.cs 66.76% <0%> (+0.64%) ⬆️
... and 7 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1103295...3799d64. Read the comment docs.

@ZacharyPatten

ZacharyPatten commented Aug 28, 2019 •

Copy link
Copy Markdown

Some types may not support all the operators. You don't want to build all the operations at once. That means if I call "Add" but the type doesn't support "GreaterThan" it would throw an exception in the static constructor.

Also, you did not add the logic operators to your code (I added them in my example here).

You should alter the code to something like this:

internal static class RuntmeCompiledFunctions
{
  internal static class Multiply<TLeft, TRight, TResult>
  {
    internal readonly static Func<TLeft, TRight, TResult> Function = CreateBinaryFunction<TLeft, TRight, TResult>(Expression.Multiply);
  }

  internal static class Divide<TLeft, TRight, TResult>
  {
    internal readonly static Func<TLeft, TRight, TResult> Function = CreateBinaryFunction<TLeft, TRight, TResult>(Expression.Divide);
  }

  internal static class Add<TLeft, TRight, TResult>
  {
    internal readonly static Func<TLeft, TRight, TResult> Function = CreateBinaryFunction<TLeft, TRight, TResult>(Expression.Add);
  }

  internal static class Subtract<TLeft, TRight, TResult>
  {
    internal readonly static Func<TLeft, TRight, TResult> Function = CreateBinaryFunction<TLeft, TRight, TResult>(Expression.Subtract);
  }

  internal static class Equal<TLeft, TRight>
  {
    internal readonly static Func<TLeft, TRight, bool> Function = CreateBinaryFunction<TLeft, TRight, bool>(Expression.Equal);
  }

  internal static class NotEqual<TLeft, TRight>
  {
    internal readonly static Func<TLeft, TRight, bool> Function = CreateBinaryFunction<TLeft, TRight, bool>(Expression.NotEqual);
  }

  internal static class LessThan<TLeft, TRight>
  {
    internal readonly static Func<TLeft, TRight, bool> Function = CreateBinaryFunction<TLeft, TRight, bool>(Expression.LessThan);
  }

  internal static class GreaterThan<TLeft, TRight>
  {
    internal readonly static Func<TLeft, TRight, bool> Function = CreateBinaryFunction<TLeft, TRight, bool>(Expression.GreaterThan);
  }

  internal static class GreaterThanOrEqual<TLeft, TRight>
  {
    internal readonly static Func<TLeft, TRight, bool> Function = CreateBinaryFunction<TLeft, TRight, bool>(Expression.GreaterThanOrEqual);
  }

  internal static class LessThanOrEqual<TLeft, TRight>
  {
    internal readonly static Func<TLeft, TRight, bool> Function = CreateBinaryFunction<TLeft, TRight, bool>(Expression.LessThanOrEqual);
  }

  internal static class Modulo<TLeft, TRight, TResult>
  {
    internal readonly static Func<TLeft, TRight, TResult> Function = CreateBinaryFunction<TLeft, TRight, TResult>(Expression.Modulo);
  }
                
  private static Func<TLeft, TRight, TResult> CreateBinaryFunction<TLeft, TRight, TResult>(Func<Expression, Expression, BinaryExpression> expressionCreationFunction)
  {
    var leftParameter = Expression.Parameter(typeof(TLeft), "left");
    var rightParameter = Expression.Parameter(typeof(TRight), "right");
    var binaryExpression = expressionCreationFunction(leftParameter, rightParameter);
    var lambda = Expression.Lambda<Func<TLeft, TRight, TResult>>(binaryExpression, leftParameter, rightParameter);
    return lambda.Compile();
  }
}

@tmilnthorp

Copy link
Copy Markdown
Collaborator Author

@ZacharyPatten I agree although it ends up a bit long-winded constantly calling RuntmeCompiledFunctions.Add<double, double, double>.Function(left, right)

Calling via CompiledLambdas.Add<double, double, double>(left, right) is possible if you write it as follows. It's a bit verbose inside the class, but the rest of the codebase is cleaner IMHO.

    internal static class CompiledLambdas
    {
        internal static TResult Multiply<TLeft, TRight, TResult>(TLeft left, TRight right) =>
            MultiplyHelper<TLeft, TRight, TResult>.Function(left, right);

        internal static TResult Divide<TLeft, TRight, TResult>(TLeft left, TRight right) =>
            DivideHelper<TLeft, TRight, TResult>.Function(left, right);

        internal static TResult Add<TLeft, TRight, TResult>(TLeft left, TRight right) =>
            AddHelper<TLeft, TRight, TResult>.Function(left, right);

        internal static TResult Subtract<TLeft, TRight, TResult>(TLeft left, TRight right) =>
            SubtractHelper<TLeft, TRight, TResult>.Function(left, right);

        internal static bool Equal<TLeft, TRight>(TLeft left, TRight right) =>
            EqualHelper<TLeft, TRight>.Function(left, right);

        internal static bool NotEqual<TLeft, TRight, TResult>(TLeft left, TRight right) =>
            NotEqualHelper<TLeft, TRight>.Function(left, right);

        internal static bool LessThan<TLeft, TRight, TResult>(TLeft left, TRight right) =>
            LessThanHelper<TLeft, TRight>.Function(left, right);

        internal static bool GreaterThan<TLeft, TRight, TResult>(TLeft left, TRight right) =>
            GreaterThanHelper<TLeft, TRight>.Function(left, right);

        internal static bool GreaterThanOrEqual<TLeft, TRight, TResult>(TLeft left, TRight right) =>
            GreaterThanOrEqualHelper<TLeft, TRight>.Function(left, right);

        internal static bool LessThanOrEqual<TLeft, TRight, TResult>(TLeft left, TRight right) =>
            LessThanOrEqualHelper<TLeft, TRight>.Function(left, right);

        internal static TResult Modulo<TLeft, TRight, TResult>(TLeft left, TRight right) =>
            ModuloHelper<TLeft, TRight, TResult>.Function(left, right);

        #region Helper Classes

        private static class MultiplyHelper<TLeft, TRight, TResult>
        {
            internal readonly static Func<TLeft, TRight, TResult> Function =
                CreateBinaryFunction<TLeft, TRight, TResult>(Expression.Multiply);
        }

        private static class DivideHelper<TLeft, TRight, TResult>
        {
            internal readonly static Func<TLeft, TRight, TResult> Function =
                CreateBinaryFunction<TLeft, TRight, TResult>(Expression.Divide);
        }

        private static class AddHelper<TLeft, TRight, TResult>
        {
            internal readonly static Func<TLeft, TRight, TResult> Function =
                CreateBinaryFunction<TLeft, TRight, TResult>(Expression.Add);
        }

        private static class SubtractHelper<TLeft, TRight, TResult>
        {
            internal readonly static Func<TLeft, TRight, TResult> Function =
                CreateBinaryFunction<TLeft, TRight, TResult>(Expression.Subtract);
        }

        private static class EqualHelper<TLeft, TRight>
        {
            internal readonly static Func<TLeft, TRight, bool> Function =
                CreateBinaryFunction<TLeft, TRight, bool>(Expression.Equal);
        }

        private static class NotEqualHelper<TLeft, TRight>
        {
            internal readonly static Func<TLeft, TRight, bool> Function =
                CreateBinaryFunction<TLeft, TRight, bool>(Expression.NotEqual);
        }

        private static class LessThanHelper<TLeft, TRight>
        {
            internal readonly static Func<TLeft, TRight, bool> Function =
                CreateBinaryFunction<TLeft, TRight, bool>(Expression.LessThan);
        }

        private static class GreaterThanHelper<TLeft, TRight>
        {
            internal readonly static Func<TLeft, TRight, bool> Function =
                CreateBinaryFunction<TLeft, TRight, bool>(Expression.GreaterThan);
        }

        private static class GreaterThanOrEqualHelper<TLeft, TRight>
        {
            internal readonly static Func<TLeft, TRight, bool> Function =
                CreateBinaryFunction<TLeft, TRight, bool>(Expression.GreaterThanOrEqual);
        }

        private static class LessThanOrEqualHelper<TLeft, TRight>
        {
            internal readonly static Func<TLeft, TRight, bool> Function =
                CreateBinaryFunction<TLeft, TRight, bool>(Expression.LessThanOrEqual);
        }

        private static class ModuloHelper<TLeft, TRight, TResult>
        {
            internal readonly static Func<TLeft, TRight, TResult> Function =
                CreateBinaryFunction<TLeft, TRight, TResult>(Expression.Modulo);
        }

        #endregion

        private static Func<TLeft, TRight, TResult> CreateBinaryFunction<TLeft, TRight, TResult>(Func<Expression, Expression, BinaryExpression> expressionCreationFunction)
        {
            var leftParameter = Expression.Parameter(typeof(TLeft), "left");
            var rightParameter = Expression.Parameter(typeof(TRight), "right");

            var binaryExpression = expressionCreationFunction(leftParameter, rightParameter);
            var lambda = Expression.Lambda<Func<TLeft, TRight, TResult>>(binaryExpression, leftParameter, rightParameter);

            return lambda.Compile();
        }
    }

You could even add this so you can deduce the parameters and just call CompiledLambdas.Add(left, right) for calls where left/right/result are the same types:

        internal static T Add<T>(T left, T right) =>
            AddHelper<T, T, T>.Function(left, right);

@ZacharyPatten

ZacharyPatten commented Aug 28, 2019 •

Copy link
Copy Markdown

The C# compiler can infer generic parameters on methods. If you want to simplify the syntax, you can wrap the calls in methods and provide overloads in which the generic parameters can be inferred by the compiler.

That is what I did in my Compute class here: https://github.com/ZacharyPatten/Towel/blob/master/Sources/Towel/Mathematics/Compute.cs

You can call it like so:

Compute.Add(1, 2);
Compute.Add(1.5f, 2.5f);

The compiler will never be able to infer generic parameters that are used for the return type though.

Edit: After re-reading your comment I realized you already mentioned allowing the compile to infertypes. Yes, that is a very good approach.

@tmilnthorp

Copy link
Copy Markdown
Collaborator Author

@ZacharyPatten yup - just looked at your example and it's exactly what I did in the above example. I'll update the code.

@angularsen

angularsen commented Sep 13, 2019 •

Copy link
Copy Markdown
Owner

I've only glanced through this thread so far, let me know when you want a review @tmilnthorp .

@tmilnthorp

Copy link
Copy Markdown
Collaborator Author

I think it's ready. Just not used yet :)

@angularsen angularsen left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some minor things, this looks awesome.

Comment thread UnitsNet.Tests/CompiledLambdasTests.cs
Comment thread UnitsNet/CompiledLambdas.cs
Comment thread UnitsNet/CompiledLambdas.cs Outdated
@stale

stale Bot commented Nov 18, 2019

Copy link
Copy Markdown

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale Bot added the wontfix label Nov 18, 2019
@stale stale Bot closed this Nov 25, 2019
@angularsen angularsen reopened this Dec 26, 2019
@stale stale Bot removed the wontfix label Dec 26, 2019
@angularsen angularsen added the pinned Issues that should not be auto-closed due to inactivity. label Dec 26, 2019
@angularsen

Copy link
Copy Markdown
Owner

Bad bot, reopening this one.

@codecov-commenter

codecov-commenter commented May 22, 2020 •

Copy link
Copy Markdown

Codecov Report

Merging #698 into master will increase coverage by 0.02%.
The diff coverage is 85.10%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #698      +/-   ##
==========================================
+ Coverage   63.87%   63.90%   +0.02%     
==========================================
  Files         279      280       +1     
  Lines       41644    41691      +47     
==========================================
+ Hits        26601    26641      +40     
- Misses      15043    15050       +7     
Impacted Files Coverage Δ
UnitsNet/CompiledLambdas.cs 85.10% <85.10%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 57122fa...2aa2f31. Read the comment docs.

@angularsen

Copy link
Copy Markdown
Owner

I've forgotten a lot of the details here, but it seems I was happy the last time I reviewed it and you have now addressed my comment, so.. I'll merge this then :-)

@angularsen
angularsen merged commit e6cfb3d into angularsen:master May 22, 2020
@angularsen

Copy link
Copy Markdown
Owner

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pinned Issues that should not be auto-closed due to inactivity.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants