Expressions and LuaXP Functions
-
It depends on what the complete expression is. Remember that the value of
eachis every non-null expression result during the iteration. So an array of arrays is a very possible response.@toggledbits Unless I'm mis-reading the way
eachworks, I had anticipated the underlying interpretation of:b=[],each i in a=[1,2]: do push(b,i) doneto be as follows:
"Start with an empty array namedb. For each item in the array nameda, starting with1, append that value into arrayb. First, you would get[ 1 ], and the next iteration, using the2froma, append that tobto yield[ 1 , 2 ]."Any I missing an important piece of the puzzle? (I do recognize that
do/doneis not necessary here, but I left it in for direct comparison with the expression immediately preceding.)Thanks. Sorry to give you grief over these operations.
-
If you want to see what b looks like at the end, you need to do this:
b=[],each i in a=[1,2]: do push(b,i) done, bIf you don't (which is how you've done it so far), you are seeing the result of
each, which is an array, and since each expression within theeachhas resulted in an array, the result of thateachis therefore an array of arrays, and referring to the same array by reference. -
This one seems to evade the purpose of
isnull()somehow:isnull(1/a) // returns falsewhereas
isnull(a) // returns true, as expectedIn both cases,
ais undefined.I also cannot seem to create an expression that results in true for
isNan(), no matter what I throw at it.Finally, why would
bool(null)return TRUE? -
null coerces to zero (0) for arithmetic operations, so
1/nullis infinity which is not null.int("abc")results in NaN which will returnisNaN(int("abc"))==truebool(null)should return false and I will fix it.Edit: just to follow up, I've added
Infinityas a keyword/value to the grammar, with a supportingisInfinity(value)test function (you can also test viavalue === Infinity), and added that to the documentation for the next release/build. Andbool(null)is fixed. FYI,bool()takes a slightly deeper view of "truthiness" than JavaScript natively: the values (number) 0, null, empty string ("") and NaN are false as in JavaScript, but so additionally in lexpjs/Reactor are string "0", string "no", string "off", and string "false"; any other value of any type is true (including empty arrays and objects). -
MSR RECIPES: Compact object-based message composer
Several of my Rules send out SMTP emails, and I'm always looking for a compact method of composing these outbound messages, using the fewest expressions along with a simple "mail merge" template. Here's a two-variable approach you might consider:
msgBody ► "This is my message." // leave blank if msgBody will be set by other Rules msgTemplate ► tmp = {hdr:"Header here" , msg:msgBody , ftr:"Footer here" , crlf:"\n"}, send = {message:tmp.hdr+tmp.crlf+tmp.msg+tmp.crlf+tmp.ftr} , send.messageI hope this can be adapted to your workflow somehow!
-
Can someone please explain how you would create an array within a Rule that does the following:
(a) begins with an expression that is an empty array;
(b) has another expression containing the .dim_level of a given light;
(c) each time the Rule sets (activates/triggers), the value of (b) is pushed into (a)Working example:
First time light gets turned on, the rule runs. Dim level of 50 is pushed onto end of [ ], making it [50].
Next time light turns on, the rule again runs and pushes the dim level of 75 onto array, making it [50,75].
Etc.I can't quite grok where to put my
push ( ). In a[Set Variable]using${{ }}substitution? In an auxiliary Expression? (I've attempted this approach and can only get one iteration along, meaning the array stops growing after 1 element.) And how does one declare an empty array inside an Expression that will otherwise be modified by the Rule, which normally requires it to be blank?Thanks!
@toggledbits , to put this more succinctly, I find myself unable to implement in MSR the construct you laid out here for creating a time series.
-
SOLUTION: This works for creating a timed data series in MSR...
test1:= if go=="1" then push( test1 , getEntity( "vera>device_110" ).attributes.dimming.level , 5 ) else test1 endif...BUT...you must first pre-populate thetest1variable with an empty array[ ],SAVE, then paste in the above definition. Otherwise, MSR will throw a "no such 'push' function on (null)" error! (I call this the "Chicken and the Egg" conundrum, whereas Reactor for Luup doesn't complain about doingpushto a non-existent array; it just invisibly creates one for you.)In my
setreaction, I do a[Set Variable] [ go ] = 1, and in theresetreaction[Set Variable] [ go ] = 0, with anIntervalimposed on the Rule's sole Trigger condition, making it goTRUEfor 2 seconds every 60 seconds.NOTE: Interestingly, with
Intervallimited to 3 repeats, the variablegochanges to"1"only 3 times (expected), yet somehow the array gets fully populated with 5 values (unexpected), so I must not fully understand the leading- and trailing-edge nature ofIntervalas it applies to Expression re-evaluation. I'm sure @toggledbits will elucidate? Or is this truly unexpected behavior?ANSWER: Unchecking "Force re-evaluation of expressions and conditions" in both the
setandresetreactions cured the problem of "too many data points". There was my problem. -
SOLUTION: This works for creating a timed data series in MSR...
test1:= if go=="1" then push( test1 , getEntity( "vera>device_110" ).attributes.dimming.level , 5 ) else test1 endif...BUT...you must first pre-populate thetest1variable with an empty array[ ],SAVE, then paste in the above definition. Otherwise, MSR will throw a "no such 'push' function on (null)" error! (I call this the "Chicken and the Egg" conundrum, whereas Reactor for Luup doesn't complain about doingpushto a non-existent array; it just invisibly creates one for you.)In my
setreaction, I do a[Set Variable] [ go ] = 1, and in theresetreaction[Set Variable] [ go ] = 0, with anIntervalimposed on the Rule's sole Trigger condition, making it goTRUEfor 2 seconds every 60 seconds.NOTE: Interestingly, with
Intervallimited to 3 repeats, the variablegochanges to"1"only 3 times (expected), yet somehow the array gets fully populated with 5 values (unexpected), so I must not fully understand the leading- and trailing-edge nature ofIntervalas it applies to Expression re-evaluation. I'm sure @toggledbits will elucidate? Or is this truly unexpected behavior?ANSWER: Unchecking "Force re-evaluation of expressions and conditions" in both the
setandresetreactions cured the problem of "too many data points". There was my problem.@librasun said in Expressions and LuaXP Functions:
you must first pre-populate the test1 variable with an empty array
You don't actually. This is just a bug in lexpjs. I've fixed it in my local copy, will be in the next build. It's just this simple:
-
@toggledbits Do you want to include conditional key references like this in lexpjs?
obj={'122':['a',5, true]}
obj?['123'][0] // null or throws error
obj?['122'][0] // (string) "a"By this I mean do you want to have square bracketed key references treated the way potentially non-existent dot references are handled using question marks.
-
Confused. Are you asking for this to be added?
-
I think so, if it's possible. Just wanted to (a) make sure that ?[ doesn't exist yet (my testing seems inconclusive), and (b) you consider it necessary. I've already bumped into a potential use case.
So, I suppose "Yes, add it" at least for consistency with existing ?. usage.
-
I think so, if it's possible. Just wanted to (a) make sure that ?[ doesn't exist yet (my testing seems inconclusive), and (b) you consider it necessary. I've already bumped into a potential use case.
So, I suppose "Yes, add it" at least for consistency with existing ?. usage.
@librasun OK... so I think I'm seeing through the confusion now...
The
?[and?.operators already exist; they were added together with??a long time ago. They are left-associative, so they refer to the operand on the left. In your examples, they will not work as you are trying to get them to becauseobjis non-null in every case (it's the first thing defined in the example). I think what you mean to say is:obj={'122':['a',5, true]} obj['123']?[0] // which will return null and not throw an error obj['122']?[0] // which will return (string) "a" -
Agree with everything you just wrote. I was mis-applying the question marks, as far as placement in my expression (treating it as a prepend instead of append). Thanks for straightening me out yet again.
I had mistakenly thought this construct only worked on
objects.?with.?arbitrary.?keysand arrays[with_arbitary_indexes]? and notobjectkeys (with non-standard names) referred to via brackets. My bad. -
CODE SAMPLE: Creating an Array of N Whole Numbers [0, 1, ... , N]
Sometimes you need an array of Counting Numbers from zero to some arbitrary integer N. But it's hard to write this as an Expression in MSR, because you don't know in advance how long to make the array, and the value of N may change later.
This somewhat byzantine construct fills the bill, and works for N=
0up to N=56:arrCount := N=56,each s in keys(substr((s='s'+1/9)+s+s,0,N+1)): int(s)This example evaluates to the array:
arrCount == [0,1,2, … ,55,56]Just change the number after
N=(it could also be another variable name, of course!) to suit your needs.BONUS: Need more numbers? Just add another
+sin the middle for an additional 19 integers (giving max. N=75). Repeat indefinitely!P.S. @toggledbits I may need to enter this in the Obfuscated Lexpjs Code contest!
-
I think it's a grand prize winner for the "Solutions for Problems Nobody Has Ever Had Using Reactor" contest, for sure, but I'll admit it's not a straightforward problem to solve without a counting loop structure, so the question is, what's the best approach:
- Implement a counting loop statement (
forin many languages); - Take the Python-ish approach and have either a
range()function or astart..endrange operator, which you could use as the operand ineach; - Other?
Choice 2 is better suited to an expression environment, but we already ride the fine edge with
each,firstanddostatements in the current language. But in keeping with the flavor, each of these statements still produces an expression result, so still arguably as much operator as statement; it's less clear what a sensible return result from "for" might be. On the other hand, #2 requires an array be built in memory over which to iterate, and an error could easily build a large array that consumes dangerously large amounts of (or all) available memory and have a negative impact on stability (so perhaps more safeguards in order, but how big is too big?).EDIT: I just prototyped the two options given above:
# Example for to build array of values 1-25 arr=[]; for i =1,25: push(arr, i) # That's unnecessarily clunky and could be streamlined by making "for" return an array # of result values like "each": arr=for i=1,25: i # But then... that means "for" is about the same as "each", just different operands. # In my mind, that kind of argues for option 2: arr=each i in range(1,25): i # or arr=each i in 1..25: i # and even that's unnecessary, because you could just do arr=range(1,25) arr=1..25 # because both of those naturally return an array. # Another example, to sum all the values from 1 to 25 it would be: sum=0; for i in 1,25: sum=sum+i sum=0; each i in range(1,25): sum=sum+i sum=0; each i in 1..25: sum=sum+iI think the range operator
..is the cleanest all around, and relieves the ambiguity of having bothforandeachso similar. I'm not sure..is any easier or harder to learn/remember than having a functionrange(start,end). So I'm leaning toward option 2 with range operator... - Implement a counting loop statement (
-
100% agree that
sum=0; each i in 1..25: sum=sum+imakes the most sense in terms of conciseness without introducing another function name. I don't foresee any usability issues with anm..noperator generally.I'd expect
..to accept any integer,–or+, for its argumentsmandn, regardless ifm>norn>m. Use cases include step functions or monotonic sequences for things like dimming levels over time.Sure looks better than what I came up with, eh?

Glad I brought it up, because I had recently been hand-coding my own arrays for use with
each. -
@toggledbits For today's checkup on how Expressions handle assigning values by reference, I constructed:
a=b=c=[1,2,3], pop(a), shift(b), [a==b,b==c,a==c,c]// result is [true,true,true,[2]]
A. Pls confirm this is as expected. (I'm guessing 'Yes')
B. Does result comport with typical user's expectations? (I'm guessing 50/50)
C. I know you don't want me mashing that "Try" ► button (in lieu of actual, useful, runtime evals).Just something that's been on my mind, wondering if it belongs in your Test Suite.







