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

Creating compiled lambda expression math class for future use #698

Merged
merged 7 commits into from
May 22, 2020

Conversation

tmilnthorp
Copy link
Collaborator

For potential future use for generic quantity structs

@tmilnthorp
Copy link
Collaborator Author

#695 @ZacharyPatten

@codecov-io
Copy link

codecov-io commented Aug 28, 2019

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
Copy link

ZacharyPatten commented Aug 28, 2019

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
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
Copy link

ZacharyPatten commented Aug 28, 2019

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
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
Copy link
Owner

angularsen commented Sep 13, 2019

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

@tmilnthorp
Copy link
Collaborator Author

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

Copy link
Owner

@angularsen angularsen left a comment

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.

@stale
Copy link

stale bot commented Nov 18, 2019

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
Owner

Bad bot, reopening this one.

@codecov-commenter
Copy link

codecov-commenter commented May 22, 2020

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
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
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