Expressions and LuaXP Functions
-
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.
-
toggledbitsreplied to LibraSun on Apr 24, 2021, 5:41 PM last edited by toggledbits Apr 24, 2021, 1:41 PM
@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 becauseobj
is 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.?keys
and arrays[with_arbitary_indexes]? and notobject
keys (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=
0
up 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
+s
in 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!
-
toggledbitswrote on May 1, 2021, 3:11 PM last edited by toggledbits May 1, 2021, 11:29 AM
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 (
for
in many languages); - Take the Python-ish approach and have either a
range()
function or astart..end
range 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
,first
anddo
statements 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+i
I think the range operator
..
is the cleanest all around, and relieves the ambiguity of having bothfor
andeach
so 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+i
makes the most sense in terms of conciseness without introducing another function name. I don't foresee any usability issues with anm..n
operator generally.I'd expect
..
to accept any integer,–
or+
, for its argumentsm
andn
, regardless ifm
>n
orn
>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.
-
You can't compare arrays (or objects) with ==. What it's telling you when you write
a==b
is that neither a nor b are null, 0, or false, and they are identical references. So yes, this is all expected.As for it comporting with user expectations, if the user is a programmer, I think this will be no surprise. Non-programmers have the learning curve. Again, another reason I really don't like guiding users to expressions. New users, IMO, should be blissfully unaware of their existence.
-
Wise words!
-
toggledbitswrote on May 2, 2021, 10:13 PM last edited by toggledbits May 3, 2021, 7:23 AM
By the way, I forgot to mention, as is often necessary in many languages because of the reference issue, there is a
clone()
function that makes a new copy of an existing array or object. Soa=[1,2,3],b=clone(a),push(b,99)
will result ina
having[1,2,3]
andb
having[1,2,3,99]
. -
It's as if you read my mind. I suppose my numerous "cloning arrays" examples gave it away.
Without
clone()
, I was never (easily) able to manipulate a copied array without affecting the original, and that was driving me bonkers.Thanks!
-
@toggledbits , am digging how the new
a..b
operator behaves predictably, fixing the decimal ofa
and spitting out integer steps up to≤b
when botha
,b
> 0 anda
<b
. For example:a = 1 .. 3.3 // result (array) [1,2,3] a = 1.1 .. 2.3 // result (array) (array) [1.1,2.1]
But sometimes it tries too hard, particularly in this case where
a
< 0:a = -2.3 .. 1.1 // result (array) [-2.3,-1.2999999999999998,-0.2999999999999998,0.7000000000000002]
or here, where
a
>b
:a = 2.3 .. 1.1 // result (array) [2.3,1.2999999999999998]
All testing thus far using only INTEGERS has avoided any rounding issues, so that will likely become my mantra on usage.
rev. 21123 -
KEEP TURNING OFF LIGHTS!
I thought it might be fun to have a Rule that periodically watches for lights (any device, really) left on, but I wanted the entire "action" part of the Rule to live inside a single expression. Here's what I came up with:
devs = each dev in getEntity( "vera>controller_all" ).members: match( dev , ".*device_\\d+" ), devices = each dev in devs: indexOf( getEntity( dev ).actions, "dimming.set" ) > -1 ? dev : null, performAction( f = first dev in devices with getEntity( dev ).attributes.power_switch.state, "power_switch.off", { } ), getEntity( f ).name + " turned off."
This example only checks dimmable lights paired with Vera, but could easily be modified to include other classes of device connected to other controllers, etc.
The way it works is it (Line 1) pulls all of the member devices associated with Vera; (Line 2) filters the list to disregard devices not of the formdevice_NNN
; (Lines 3-4) checks to see whether each device handles dimming; and, (Line 5) passes along only those forperformAction()
to turn off individually (one "On" device per cycle); finally, (Line 6) the name of the most recently turned-off device is logged.I concocted this behemoth because my Vera routinely chokes on my "Lights Out" rule, which attempts to switch 20 devices OFF all at once. I felt it might be more prudent to handle the switching at a slower pace, with the added benefit that if anyone turns ON one of the listed devices during "Lights Out" time, this rule would spot it and turn it back off automatically.
Comments?
- Libra
-
I suggest adding both comments (
#
to end of line is a comment in lexpjs) and indent spaces for clarity (e.g. thematch
clause on the second line is part of theeach
on the first line, so IMO an indent would make this much more clear).That search on the first two lines suggests that MSR may need a deterministic way to differentiate an entity that represents a device from entities that represent other system data. I'll think about that, because it has come up before.
Another way to write the test on line 4 is
'dimming' in getEntity( dev ).capabilities
. Thecapabilities
property is an object, so thein
operator works by directly testing on that object, and will be much faster than usingindexof()
on the actions array. Given, this operation probably would not run frequently, so that kind of optimization may be overkill, but I assert that this alternative to the array search is actually easier to interpret as well, and where performance is not the leading goal, clarity is a good next candidate.The inline assignment to a temporary variable on 5 and used in line 6 is clever and makes me cackle with glee.
-
@toggledbits and other puzzle lovers... I challenge you to paste in this Expression definition and run it with the 'try' button, to solve this heavily obfuscated conundrum:
solve="<creator_mystery>"; s1="(.)";s2="(..)";s3="(...)";s4=s1+s1;s5=s2+s2;s6=s3+s3;who=getEntity(replace(solve,s4+s6+s4+s4+s5+s1,"\$3\$2\$4\$5\$8\$7\$8\$9\$6\$11\$8\$7\$8\$9\$6")); you=who[keys(who)[0x07]];are=you[keys(you)[0b01]];upper(substr(are[keys(are)[0o10]],0,0o13))
|-Answer: _____________°fa-info°(S┴IqpƎ˥פפO┴)-|
-
@toggledbits , how does MSR "know" that an expression like:
each dev in devs: getEntity(dev).attributes.power_switch.state ? dev : null
contains dependencies on the status of (potentially) many devices?
It's neat that MSR almost immediately detects when a light gets turned on, for instance, and the resulting array grows by one within less than a second. But what is cuing MSR to re-evaluate the above expression so quickly behind the scenes?
-
If
devs
is a constant list, the iteration visits eachdev
throughgetEntity()
, and that function subscribes to any device it is asked to resolve. So the first time the expression is evaluated, it has subscribed to every device indevs
. -
@toggledbits meanwhile, as I watch the steady stream of "changed entities" (involving devices that are otherwise just minding their own business, doing nothing) going by on
Status
, such as:Sound Bar Beam vera>device_322 Playing 17:39:11 Sofa Lamp vera>device_138 false 17:39:00
do you recommend any changes to Vera in order to throttle that traffic, or is it just customary Z-Wave polling in action?
-
The rule or expression isn't reacting to every device, only those devices that end up being a target of
getEntity()
. The polling activity will cause additional evaluations, but it causes little actual additional load on the (MSR) system, certainly not enough to make it worth your time to try to put a dent in it. Party on! -
SORTING AN ARRAY IN MSR EXPRESSIONS
I somehow doubt this will find much utility among most users' workflows, but here goes...
If you want to sort a particular 1-dimensional numerical array:a=[8,3,11,0,5], #unsorted array L=len(a)-1, each i in 0..L: #repeat for each value in a do b=a[0], #beginning with first value in array each j in 0..(L-i): #compare remaining values if min(b,num=a[j])<b then b=num else b endif, remove(a,indexOf(a,b)), #remove b from array b #was lowest number found in original array done
// result (array)
[ 0 , 3 , 5 , 8 , 11 ]
PRO TIP: To sort an array from another expression, simply edit the first line to something like this:
a = clone(otherExp),
UPDATE: As of release 21153, in which
min()
may take an array as its argument, the following expression works more cleanly:a=[8,3,11,0,5], each n in a: do b=min(a), remove(a,indexOf(a,b)), b done