Great Project... Some initial thoughts

Mar 14, 2009 at 6:11 AM
This is great... I was drawing out a problem on a piece of paper today and knew I needed an expression evaluator.

Finding a good one is hard... My first thought was nVelocity, but it is so heavy I hated to use it (although I did test it).

Then I found FLEE and I'm pretty well satisfied. 

There are a few things that have come out in testing though:
  1. Support for nullable types (Nullable.GetBaseType)?
  2. Support for decimals
If I can get the code to convert to c#, I might can add some of this myself.

Once again, a great project!

Thanks,
Dustin
Coordinator
Mar 14, 2009 at 7:27 PM
>I was drawing out a problem on a piece of paper today and knew I needed an expression evaluator.
What kind of problem was it?  I'm always interested in scenarios where Flee is useful.

>Support for decimals
Flee doesn't support decimals directly but it does recognize the overloaded and implicit operators defined on them and thus allows them to be used in expressions.  It doesn't support decimal literals (ie: 100D + 200D) though, is that what you meant?

>Once again, a great project!
Thanks!
Mar 16, 2009 at 4:46 PM
I'm designing an alert system for an inventory management / order manager system.  Basically, the goal is to be able to dynamically declare "Alerts" or business rules and have them perform defined actions when the dynamic condition is met.

Some example expressions I've put together:

Test if price is appropriate based on cost and price:
if(ToDouble(Object.Price)/ToDouble(Object.Cost) < 1.49,"true","false")

Test the total weight of the boxes is above 800 and isTruckOnly=false to see if shipping is appropriate:
if(sum(Object.BoxesCollection,"Weight") > 800 and ToBool(Object.IsTruckOnly,false)=false,"true","false")

So one final thing I'm dealing with is I want to compile these expressions and just pass in Object at runtime.  Unfortunately, if I use the event handler method, then the original instance of the object is always used. So, I'm left with statically defining the event handlers and using a singleton style class to prevent anything from changing the static variable while the alert is processing... I might could do this by thread instead of a singleton class.

Example:

        static void Variables_ResolveVariableValue(object sender, ResolveVariableValueEventArgs e)
        {
            e.VariableValue = currentSavedObject;
        }

        static void Variables_ResolveVariableType(object sender, ResolveVariableTypeEventArgs e)
        {
            e.VariableType = currentSavedObjectType;
        }

And then for the work:

                    IDynamicExpression dynE;
                    if (!expressionCache.TryGetValue(a.AlertID, out dynE))
                    {
                        ExpressionContext context = new ExpressionContext();

                        //helper types
                        context.Imports.AddType(typeof(AlwaysConvert));
                        context.Imports.AddType(typeof(Collections));

                        context.Variables.ResolveVariableType += new EventHandler<ResolveVariableTypeEventArgs>(Variables_ResolveVariableType);
                        context.Variables.ResolveVariableValue += new EventHandler<ResolveVariableValueEventArgs>(Variables_ResolveVariableValue);
                        try
                        {
                            dynE = context.CompileDynamic(a.AlertCriteria);
                            expressionCache.SafeAdd(a.AlertID, dynE);
                        }
                        catch (Exception ex)
                        {
                            log.ErrorFormat("Invalid expression for criteria on Alert ID {0} for Object Type {1} Criteria {2} Reason {3} Stack Trace {4}", a.AlertID, objectType.ToString(), a.AlertCriteria, ex.Message, ex.StackTrace);
                        }
                    }
                    try
                    {
                        string result = AlwaysConvert.ToString(dynE.Evaluate());
                        alertValid = AlwaysConvert.ToBool(result, false);
                    }


Is there a better way?

Thanks,
Dustin

Coordinator
Mar 19, 2009 at 1:44 AM
Why can't you just use the object instance as a variable?  Then you can swap in another instance and re-evaluate.  The Resolve events are usually used when you don't know what variables the expression will reference but in your case there seems to be only one main object.
Mar 20, 2009 at 11:43 AM
Ah... I see, you are correct.  I didn't realize that IDynamicExpression.Context was not readonly.

This works great with this method.

I appreciate the help.  Any other suggestions for maximum performance?

Thanks,
Dustin
Coordinator
Mar 21, 2009 at 7:20 PM
>Ah... I see, you are correct.  I didn't realize that IDynamicExpression.Context was not readonly.
Yeah, most of it is read-only except the variables and the expression owner which are live.  Being able to swap in new values for variables without having to re-compile the expression is a key requirement.

>Any other suggestions for maximum performance?
You could put your main object as the expression owner.  The expression then becomes like an instance method on the object.  So instead of "Object.Price" you can write just "Price".  Loading the expression owner should be slightly faster than loading a variable.

The other tip would be to make the object properties strongly-typed so that you don't have to call your conversion functions on them during each evaluation.
Mar 23, 2009 at 2:09 PM
Loading the expression owner is faster, almost twice as fast... We're talking about arc seconds, but still.

The reason I have to call the conversion is because Price is a decimal.. So if Cost is a double, Flee throws an error:

ArithmeticElement: Operation 'Divide' is not defined for types 'Decimal' and 'Double'

Also, if I have a nullable property (i.e. Cost2 could be a nullable decimal or double), Flee throws this exception:

ArithmeticElement: Operation 'Divide' is not defined for types 'Decimal' and 'Nullable`1'

So, the only way I know to fix this is a conversion function that always returns a valid value even if the property is null...

So, ToDouble, ToDecimal, etc are all defined for that purpose...

Thoughts?

Thanks again!


Mar 23, 2009 at 2:15 PM
I could omit the ToDouble(Object.Cost) in the below example:

if(ToDouble(Object.Price)/ToDouble(Object.Cost) < 1.49,"true","false")

But not on the Price
Coordinator
Mar 26, 2009 at 2:09 AM
There is an explicit conversion defined for double <-> decimal, so you could write the following:
cast(Object.Price, double) / Object.Cost
or
Object.Price / cast(Object.Cost, decimal)

Nullables also support an explicit conversion to their underlying type so the same trick would work.
Jun 1, 2011 at 9:50 PM
vivitron wrote:
This is great... I was drawing out a problem on a piece of paper today and knew I needed an expression evaluator.

Finding a good one is hard... My first thought was nVelocity, but it is so heavy I hated to use it (although I did test it).

Then I found FLEE and I'm pretty well satisfied. 

There are a few things that have come out in testing though:
  1. Support for nullable types (Nullable.GetBaseType)?
  2. Support for decimals
If I can get the code to convert to c#, I might can add some of this myself.

Once again, a great project!

Thanks,
Dustin

I had the same issue today but I was able to make a change to the MemberElement class.  Go to the GetMemebers function and change

the return statement:


Return MyPrevious.TargetType.FindMembers(targets, BindFlags, MyOptions.MemberFilter, MyName)


TO:


If Nullable.GetUnderlyingType(MyPrevious.TargetType) IsNot Nothing Then

  Return Nullable.GetUnderlyingType(MyPrevious.TargetType).FindMembers(targets, BindFlags, MyOptions.MemberFilter, MyName)

Else

 Return MyPrevious.TargetType.FindMembers(targets, BindFlags, MyOptions.MemberFilter, MyName)

End If

-- Shayne

Sep 8, 2011 at 2:44 PM
vivitron wrote:
I could omit the ToDouble(Object.Cost) in the below example:

if(ToDouble(Object.Price)/ToDouble(Object.Cost) < 1.49,"true","false")

But not on the Price

Just in case it helps:

You can use the 'm' suffix to get a literal decimal and avoid the cast, so ... < 1.49m ...

That defo works for me.

Actually, while I'm here, setting this doesn't seem to work
context.Options.RealLiteralDataType = RealLiteralDataType.Decimal

Not an issue as the 'm' works fine but thought I'd let you know.

Cheers,

Rob.