Reusable Functions: A simple lambda example with copious notes

lambda
Tags: #<Tag:0x00007f212f283528>

(Rich Koshak) #1

Major Edit: Rewrote with the cleaner OH 2.3 syntax.

The example lambda on the OH 1 wiki is overly complex and as written will not work in OH 2. This quick example will show a super simple lambda example with copious notes.

Here is the code without comments. It takes in an Item, logs it’s state and returns a slightly modified version of the String logged.

val log= [ GenericItem s |
    logInfo("lambda", s.state.toString)
    s.state.toString + " logged"
]

rule "Call Lambda"
when
    // some trigger
then
    val loggedStr = log.apply(MyItem)
end

Now with explanation.

// Previous to OH 2.3 you had to import Functions or Procedures. You no longer need to import these.

// A lambda is a global variable. I use a val to make it a constant since we are not going to 
// reassign it.
//
// A Lambda has a limitation of no more than 6 arguments.
//
// Lambdas have absolutely no context. Any other global vars or vals (including other lambdas)
// must be passed in as an argument. Lambdas do have access to Actions and all your Items 
// though so those do not necessarily need to be passed in.
// 
// In the below there is a function that takes one GenericItem as its argument. Unlike the wiki
// example, there is no need to use the fully qualified class name and in fact, because all the 
// core classes have moved to the Eclipse SmartHome packages the code on the wiki will not
// work in OH 2.
//
// Note that we are specifying the type. The Rules DSL will determine the type (Functions versus Procedures)
// based on the value of the last line(s) executed (if the lambda has more than one last line they must
// all return or be of the same type. If the type returned by the last line is void the lambda will be a
// Procedure.
val log = [ GenericItem s |

    // log the passed in Item's state. Note we did not have to pass in the logInfo Action
    logInfo("lambda", s.state.toString)

    // The result from the last line of the lambda will be returned.
    s.state.toString + " logged"
]

// Some rule that uses the lambda. It isn't worth writing a lambda if you do not have more
// than one rule the needs to call it.
rule "Call Lambda"
when
    // some trigger
then
    // To call a lambda you use .apply and pass the arguments
    val loggedStr = log.apply(MyItem)
end

For a potential alternative to lambdas in some circumstances see:


Example of a Lambda Function for timing the turning on and off of Lights (or switches)
Lambda example from Wiki doesn't work?
Can a rule trigger another rule
Motion detection -> lights on - if it's dark and within time period, with timer - Help needed
Rules issue in OH2. Is it my rules or OH2?
Difference between scripts and rules
Rules / functions / scripts: best approach
Reuseable functions -> Error while using postUpdate
Issue with final parameters when using lambda to create "function"
Lambda Procedure error: java.lang.NullPointerException
Unit-Support, KNX 2, Karaf 4.1.5 Upgrade and more!
Use variable for item
Use Array-Items and one Rule
Rule Parsing error -fixed-
Intergas Incomfort Lan2RF rules
Calculate periodic consumption of gas based on meter readings
Is there a **simple** lambda example?
Lambda function EOF and other errors
Design Pattern: Motion Sensor Timer
"Pass by reference" calling object to rule?
Group state not updating, reboot solves it
Global functions for use in rules
Escaping string in 'when' clause
Scripts Folder?
Is there anything like global rules?
Rules stop firing after a short while, stack trace in logs
Help with Rules please
Best practice for .rules
[SOLVED] Lambda not being called
Rule for batch processing of input/outputs
Config file loading order, any way to make items load first?
Blinking LED - how to implement it correctly?
Passing parameters to a script
New Binding - Timer/Scheduling Binding
Rule Errors with OpenHab2
Best way to control timer from more than one .rules file?
Refer to Items by value or variable in Rules? (Code portability.)
Design Pattern: Working with Groups in Rules
Same rule for 2 proxy items
Format Number Item
Best way to make long time macro
How to calculate average dates of when an item has been switched on?
Using Thread to wait until some condition happens
Any chance of sanity checking in zWave stats?
Timers in rules
createTimer inside a lambda
Xtend Scripts vs JSR223?
(Martin) #2

Does your example work in OH2?
All of mine lambda function calls fail on OH2.


(Rich Koshak) #3

Yes


(Martin) #4

Did you have to change the syntax somehow or did they run unchanged.


(Rich Koshak) #5

The only changes I had to make, which applies to all my code, not just lambdas, was remove imports for core OH classes and remove any fully qualified names for the same classes.

I also added a classname to the variables in my forEach clauses but that was to make Designer happy and was not required to run.

Since I didn’t use fully qualified class names in the first place most of my lambdas worked unchanged.


(Rich Koshak) #6

Added a section with updated syntax to get rid of the ESH Designer warnings.


(Martin) #7

Interesting, is the ESH Designer working again?
If I am trying to use this tool it already fails when pointing it to the new folder structure.


(Rich Koshak) #8

The older 0.8 version works. That is currently the version available for download on the ESH website. If you have the 0.9 you can/should downgrade and the problem with Items and Actions will go away.


(Martin) #9

Thanks. Indeed this is “somehow” working.

I am getting tons of false error message.
“say”, “push notification” and other key word are flagged and
every statement which contains “as DecimalType” is also flagged with an error.

But the good news is that this version seems to be able again to cope with longer rule files. In my case 2400 lines of code. At least on MacOS. I did not test Designer on Windows 10 yet.


(papabrenta) #10

Rich Koshak, thanks for this example. I’m finally getting a glimmer of understanding lambdas. :relaxed:

In follow up, I have summary of how I see this function & rule working & a tweak I wanted & worked out.

How I understand this: The function named “log” expects an item name & will cast strings. s stands for the rule-supplied item. The function creates a string which is the state of a rule-supplied item (here, MyItem) That string is sent to the log file named lambda & will display in a terminal window by the command tail -f /var/log/openhab2/openhab.log. According to a comment in the OLD syntax, the function’s last line, the string version of MyItem’s state + " logged" is returned to the rule that called the function & that created string populates val loggedStr in the rule.

With some research, I learned that MyItem.name will supply the item’s name.
If I tweak the function’s 2nd line to
logInfo(“lambda”, s.name + " = " + s.state.toString)
the lambda file will get a line like MyItem = OFF & a tail -f command will display that in Terminal.

I like logging not only the state, but also the name of the item.

From my experiments, the function’s last line (which returns to loggedStr in the rule) could also be tweaked to include things like MyItem.name. loggedStr could in turn be used after the function call in the rule.

This function could be a ready made method to call for logging items’ states . It also opens other possibilities for using functions to handle other often used methods.

How am I doing on understanding this lambda & adapting it?

Thanks again for the tutorial & launching me on a good learning process.


(Rich Koshak) #11

Incorrect. The lambda expects an Item. Not an Item name (which would be a String, not of type GenericItem). It doesn’t cast anything. It calls the toString method on the State returned when calling the .state method on the passed in (I.e. s).

Sorry of. Lambdas take 0 or more arguments. Reach argument is given a unique name. This name is exactly like a valid in your rules. So s is a val that contains the passed in Item.

Yes, see my description above.

Sort of. The string is sent to the logInfo action which is, as far as the rule is concerned is a function call. What happens from the depends on how logging is configured. By default the line will be written to openhab.log.

That is true in both the old and new syntax.

[quote=“papabrenta, post:10, topic:15888”]
I like logging not only the state, but also the name of the item. …
This function could be a ready made method to call for logging items’ states . It also opens other possibilities for using functions to handle other often used methods.[/quote]

Using a lambda for something like the example above is not a very good idea. I chose this example because it was the easiest to understand that I could think of. In practice you would almost never want a lambda for something this trivial. See my Separation of Behaviors for a better approach. And even then that use case is if you have complicated logic you want to apply (e.g. log to different places based on time of day). If all you are doing is logging out an item’s state it should be in line, not separate.


(papabrenta) #12

Thanks, Rich, for the detailed responses & corrections. Having not “come from Java,” I am (obviously) inexperienced in this type of programming & using the right terms, but I’m trying to learn. I am curious about lambdas, how they work, where they might be useful. I found your “Reusable Functions…” to be more approachable than other stuff that used lambdas without much explanation.

Followup on “A simple lambda example”: So inside “< >,” GenericItem means the lambda expects the call to pass it an item. Sounds like “String” inside < > means the function expects to pass back a string as a return value. Is that correct?

I looked again at “Separation of Behaviors” & see you recommend use of proxy items & the like instead of lambdas. I was already using proxy items & lambdas confused me. Maybe I should usually stay with what I was doing before.


(Rich Koshak) #13

Be careful thinking about the Rules as Java. While Java classes are available, the Rules language is very unlike Java in many ways, including lambdas.

You are correct, the last class listed inside the <> is the return type.

Thanks. That was the intent of the posting.

Note the two are not just two different ways to do the same thing. Both have problems they are better suited for than the other. Separation of Behaviors (SOB) is great when you have logic that you need to use globally across all your rules files (e.g. alerts). Lambdas are best when you have common logic that applies to more than one rule within the same rule file and there is no way to handle the common logic by merging rules.

SOB is asynchronous and runs at the same time as the rest of your rule. Lambdas are synchronous and prevents the rest of your rule from running while the lambda is running.

SOB can be “called” from any rule file. Lambdas can only be cashed from the same rule file.


(papabrenta) #14

Rich, I believe that in the conversation here, I’m getting a bit clearer on understanding & using the Rules language, lambdas, & proxy items, Thanks again for taking time to respond soon & in detail. I’ll definitely bookmark “Reusable Functions” for future reference.


(Steve) #15

Great demo, thanks! I’m new to lambdas, and haven’t found the info elsewhere, so forgive me if it’s been covered, but is it possible to pass multiple items in one argument, and loop through them? Kinda like how you’d loop through grouped items? Say…

Lambda:

val Functions$Function1 lightsOff= [ GenericItem s |
      s.members.filter( x | x.state == ON).forEach[Switch |
            Switch.sendCommand(OFF)
      ]
]

Rule:

val loggedStr = lightsOff.apply((itemSwitch1, itemSwitch2, itemSwitch3))

(Rich Koshak) #16

You can create a Map or a List and pass the List as an argument. That is about all you can do. I know of no way to define a lambda that takes an arbitrary number of arguments.

That would looks something like:

import java.util.List

val Functions$Function1<List<SwitchItem>, Boolean> lightsOff = [ lights |
    lights.filter[light | light.state == ON).forEach[light | light.sendCommand(OFF)]

    true
]

NOTE:

  • The above follows the updated syntax and will avoid errors in your logs
  • A Functions lambda must return a value. The return value is the result of the last executed line in the lambda. As written, OH will complain that the lambda doesn’t cause and side effects or something like that because the forEach doesn’t return anything, hence the addition of the true.

(Steve) #17

Perfect, I’ll give it a try. Thanks!


(HomeAutomation) #18

@rlkoshak:
I tried your last example. But it does not work:

I think there is an syntax error in your post?

import java.util.List

val Functions$Function1<List<SwitchItem>, Boolean> lightsOff = [ lights |
    lights.filter[light | light.state == ON].forEach[light | light.sendCommand(OFF)]

    true
]

Behind the == ON there must be an ‘]’ instead of ‘)’?

But how to start the function. If I do it like this in rule file

val loggedStr = lightsOff.apply((itemSwitch1, itemSwitch2, itemSwitch3))

I get in log:

2018-01-31 14:43:32.186 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'lambda.rules' has errors, therefore ignoring it: [28,28]: missing ')' at ','
[28,53]: mismatched input ')' expecting 'end'

Looks like syntax is wrong.


(Rich Koshak) #19

Yes there is a typo. You have it correct. It should be a ], not a ).

Why the extra parens around the arguments? Are you trying to create a List? If so I don’t think you can do it like that. You have to do something like:

val List<SwitchItem> args = newArrayList
args.add(itemSwitch1)
args.add(itemSwitch2)
args.add(itemSwitch3)
val loggedStr = lightsOff.apply(args)

Even if you can create a List like that, it would be a List, not a List so even if the syntax were right, the types would have been wrong.