[SOLVED] findFirst[ ] from Group doesn't find a match

From a group that is defined as

Group:Number:MIN gSpritpreise "Spritpreise [%.2f]"

I’m trying to get the Item with the lowest value in a rule using

cheapestItem = gSpritpreise.members.findFirst[ p | p.state == gSpritpreise.state ] as NumberItem

While the syntax works as expected for many other groups an error is thrown for this one.

Error during the execution of rule 'Test': An error occurred during the script execution: Couldn't invoke 'assignValueTo' for feature JvmVoid:  (eProxyURI: test.rules#|::0.2.0.2.0.1::0::/1)

I checked that all the Items in ‘gSpritpreise’ (all are Number Items, of course) and the group-Item itself (gSpritpreise.state) has a set value. Also there is/are Item(s) that match the condition in findFirst[ … ]. Writing gSpritpreise.state to a separate variable first and adding var or val before the Item definition doesn’t help. Any ideas?

There’s the potential for a race. If any member of the group changes, it will take time to recalculate a new MIN state for the group.
As I understand, that can result in several group state changes as the system iterates through members - not sure?
But either way, potentially there no member state matches to group state.

Think you’ll need to check for a null result before trying to use it. If all members are numbers, you don’t need that cast.

That might be an issue in a rule where I use ‘Member of gSpritpreise’ as a trigger (which indeed I want to do later). In this case, though, I’m testing with a rule which runs automatically every 15 secs. So the potential issue you describe should not occur here, I guess, since these Items don’t change states often. I also tried using a hard-coded value (e.g. 1.199). Doesn’t help either.

Thanks for the hint.

Well, you don’t have to check for null results, it’s your secret rule.

I’m a bit curious how closely MIN might match a member state, I wonder if 2 will match 2.0 etc. Numbers can be a bit funny in rules.

Maybe the error doesn’t come from that line.

gSpritpreise.state comes from the corresponding Items. Thus, it should match at least one Item’s value. Also, as pointed out in my original post, I checked without gSpritpreise.state as a condition and hard-coded a number instead. Didn’t work either.

I removed everything else but this line in the rule. Thus, it is the source of the error.

Is cheapestItem undeclared then? I would expect a different error message.

cheapestItem is a local variable defined in the rule only. I don’t define it anywhere before. It works fine just this way in many other rules where I match Item names against strings. This here is the first time I try to match an Item state against a number.

My point with that was that MIN is a calculated value, like say AVG. I’ve no idea of the nuts and bolts but can easily imagine it producing 2 from an Item 2.0 or vice versa, and that sometimes causes problems e.g some persistence will save 2 and restore 2.0, they are not identical.
It’s just a suggestion as to why you might not get a match.
If you intend to use this group search technique it would pay to verify how that pans out.

Okay. You said that other was the only line in the rule, hence the question. So your rule then-block is now

var cheapestItem
cheapestItem = gSpritpreise.members.findFirst[ p | p.state == gSpritpreise.state ]

And the jvmvoid error persists, right? I cannot see the problem with that, it might return null but nothing would try to use that.

I shall do some testing then. However, at least when I output the values from the individual Items and the Group-Items to the log they do match.

I don’t use

var cheapestItem

When I do, the original error disappears, but I can’t use the Item with .state, .name or similar. The error

Error during the execution of rule 'Test': 'state' is not a member of 'cheapestItem'

appears. When I then just use cheapestItem without anything the log says:

Error during the execution of rule 'Test': An error occurred during the script execution: Could not invoke method: org.eclipse.xtext.xbase.lib.ObjectExtensions.operator_plus(java.lang.Object,java.lang.String) on instance: null

This probably means that indeed it can’t find a match, thus the Item is null, but it doesn’t solve my problem. I’ll change the name of the topic. :wink:

Well that was all a wasted effort.

Going back to your actual requirement, you just want to find the minimum value in a group of number Items? (i.e. you don’t really care about matching MIN)
Do you want to include or exclude NULL or UNDEF states?

I shall summon @rikoshak who knows a few nifty group filter tricks

Well, the thread title was maybe ill-posed by including the error message. But the goal was stated quite clearly.

Beyond that I only described my observations. I do have a work-around solution by now through looping over all group members and writing Item states to local variables. That’s however far messier code and as far as I understand it should work using findFirst as well.

Thanks for your help so far, @rossko57.

This is correct. Each step of the recalculation results in an update so there will be N-1 updates where N is the number of members in the Group.

It will match, but only if 2.0 is actually 2.0. The way that floating point numbers are represented, not every possible floating point value can be represented. Consequently when you have a value that can’t be represented (e.g. 9.2) you end up with a value that is really close to 9.2. But all those little errors can add up so it’s really easy if you’ve done a calculation where you have the value 2.0000003 instead of 2.0 and if you use == to the integer 2 it will return false. This is why, as a general rule, == and != should never be used with floating point numbers except in cases where the floating point number is rounded first.

In this case though, dancemichi is right. The state of the Group that get’s calculated should exactly match the state of one of the Items because there isn’t any math taking place. It just postUpdate’s the Group’s state with the state from the min Item. I don’t think a conversion or the like is at play here.

Almost, but I’m a tricky deamon. You have to get my name exactly right. :japanese_ogre: :smiley:


The error doesn’t make a whole lot of sense to me. If findFirst doesn’t find a match, it returns null, not void. How did you declare cheapestItem? Did you give it’s type? The type of the variable get’s defined either explicitly, or by inferring the type based on the value that get’s passed to it when it get’s declared.

If you declare:

var cheapestItem

I think cheapestItem’s type will be void since you didn’t give the language any other idea about what type it should be.

var cheapestItem = null

I’m not sure what type cheapestItem will be. I don’t think it will be void but it doesn’t have anything else to infer a type. Maybe it would be Object.

In order to set the type correctly you need to supply it. Either

var NumberItem cheapestItem

or

var cheapeastItem = gSpritpreise.members.findFirst[ p | p.state == gSpritpreise.state ] as NumberItem

Both of these lines tell the Rules engine that cheapestItem needs to be a NumberItem.

on instance: null

This error is somewhat misleading. When the Rules Engine in unable to convert the type of a variable to one appropriate for the operation you are trying to do, a null exception is generated. Thus, it doesn’t necessarily mean that the findFirst failed to return a value . It could be the value returned cannot be used with the + operator.

I would expect

val cheapestItem = gSpritpreise.members.findFirst[ p | p.state == gSpritpreise.state ] as NumberItem

to work.

2 Likes

Now that’s literally unbelievable. It does work! Well, I initially forgot to use ‘val’ or ‘var’ at the beginning of that line (that’s when the above described ‘assignValueTo’ error occurred), but I could make an oath I tested that line before after comparing to my own usage of findFirst in other rules, where it does work as expected. I began to suspect that it indeed has to do with the matching condition comparing numbers, which I weren’t in need of before.

I’m new to this forum as an active user, but have followed many threads before. So maybe it’s just some of @rlkoshak’s magic going on here. :wink:

Thanks a lot!

Don’t forget the race still exists, leaving you with a potential no-match.
The longhand iteration and compare to find a current minimum will at least find something, with the possibility it’s a few milliseconds out of date.

(Now I’m academically curious as to which of a member at 2 and a member at 2.0 will be seen as MIN :wink:)

1 Like

Maybe we are lucky and @rlkoshak even knows whether a group’s state is calculated upon an update of one or more of its members before any rules are triggered, or not.

Well, what’s your rule triggered upon? Group state change - see earlier about how the group state changes. Indeterminate how far you might get through any triggered rules while the group calculation is in progress. Can’t wish this race away.

You can avoid it by introducing a delay between trigger and your search.

Currently the rule is triggered upon a change of state in any member of the group. Using the group itself as a trigger would run the rule multiple times, since…

Didn’t think of that. That should circumvent the potential problem. Nice idea. Guess I could start a 1 sec. Timer or something inside the rule.

Ohh less than that - try just 20mS sleep (probably 5 times what it needs to be)

Of course a second change might still happen along :crazy_face:
I’d still code for a no-match,no action failsafe if possible.