How to use Lambda recursively

  • Platform information:
    • Hardware: Raspberry Pi 3b
    • OS: Openhabian
    • Java Runtime Environment: OpenJDK Runtime Environment (Zulu8.36.0.152-CA-linux_aarch32hf) (build 1.8.0_202-b152)
    • openHAB version: 2.4

Hi all,

I’m trying to itterate through all items of a group. If the current item is a DimmerItem I want to set a Level and if it is a ColorItem I want to set a Color.
But is the current Item is a Sub-Group, I have to dive into this sub-group and Analyse those member again.
Usually this is a perfect Situation to use recursion.

I was able to design a Lambda Expression to Analyse the members of a Group, but I’m struggling when recursively calling the Lambda Expression insode itself.

import org.joda.time.*

// *********** LAMBDAS ***********
val Functions$Function3<GroupItem, GenericItem, GenericItem, Result> applySettings = [ GroupItem s, GenericItem b, GenericItem c |
    var group = s 
    var color = c
    var brightness = b
    var applySettings = true
    val boolean logLambda = true
    val String logPrefix = 'Example LAMBDA - '
    logInfo('rules', logPrefix + '--------------------- Lambda Begin ---------------------')
    logInfo('rules', logPrefix + ' >>> ' + group + ' -->  Class: ' + group.getClass().getSimpleName())
    group.members.forEach[member|
        if (member !== null) {
            logInfo('rules', logPrefix + ' >>> ' + member + ' Class:' + member.getClass().getSimpleName())

            switch (member.getClass().getSimpleName())
            {
                case "DimmerItem" : {
                    logInfo('rules', logPrefix + 'Applying Brightness: ' + brightness)
                    sendCommand(member, brightness)
                }
                case "ColorItem" : {
                    logInfo('rules', logPrefix + 'Applying Color: ' + color)
                    sendCommand(member, color.state)
                }
                case "GroupItem" : { 
                    logInfo('rules', logPrefix + 'Recursion starts here')                   
                    applySettings.apply(member, b, c)          // <-- **Here shall the Lambda call itself recursively**
                }
                default:
                {
                    logInfo('rules', logPrefix + 'default: ' + member + ' Class:' + member.getClass().getSimpleName())
                }
            }
        } 
        else 
            logInfo('rules', logPrefix + member + ' = null')       
    ]
    logInfo('rules', logPrefix + '--------------------- Lambda END ---------------------')
]


rule "Testrule"
when
    Time cron "0 0/1 * ? * * *"
then
    val String logPrefix = 'Example - '
    var group = G_LightOut
    logInfo('rules', logPrefix + '************************ Rule Begin ************************')
    applySettings.apply(group, 80, BeleuchtungStrasseObenColor)
    logInfo('rules', logPrefix + '************************ Rule End ************************')
end 

Any Idea how to get the recursion working?

Thanks a lot,
Thorsten

First of all, Lambdas are not functions. They are Object that can be called as a function. Why is this important? It’s because there is no stack when using lambdas. With no stack that means that your recursive call will actually overwrite the variables from the previous call. Recursion is rarely the best solution (unless you are using Lisp) but with lambdas it would be disastrous.

Secondly, if we assumed that recursion could work, it’s important to realize that there is no context for globally defined variables. This means that global variables cannot reference each other (or themselves). When you create your lambda, there is not such thing as a variable named “applySettings” so it doesn’t exist inside your lambda to call recursively. So if the first issue were not a problem, you would have to pass the lambda to itself as an argument. But since the recursion wouldn’t work anyway I won’t go through an example.

It’s also worth mentioning here that lambdas are not thread safe so they are not all that great to use in this way because if it gets called by two separate Rules at the same time they will stomp over each other. And if you can’t call the lambda from multiple Rules at the same time, the use cases where they can be used safely is a very small list.

Thirdly of all, this lambda is far to complex. You don’t need the recursion nor do you need the Switch statement. Call getAllMembers and you will get a list of all Items from all the subgroups as well as the Items that are a direct member of the Group so there is no need for the recursion in the first place.

group.getAllMembers....

Then use filter to narrow down the list to just those types you care about.

group.getAllMembers.filter[ i | i instance of DimmerItem] {
    logInfo('rules', logPrefix + 'Applying Brightness: ' + brightness)
    i.sendCommand(brightness)
}
group.getAllMembers.filter[ i | i instance of ColorItem] {
    logInfo('rules', logPrefix + 'Applying Color: ' + color)
    i.sendCommand(color.state)
}

Finally, there is an even better approach that eliminates the need for the lambda entirely (though you haven’t given enough context to show that the lambda is necessary in the first place). Just put your Dimmer Items and Color Items into their own Group. Then you can send the command to the Group.

G_LightOutDimmer.sendCommand(80)
G_LightOutColor.sendCommand(BeleuchtungStrasseObenColor.state)

Items can be members of multiple Groups at the same time. Adding extra Groups is a very small price to pay to eliminate 36 lines of code and a lambda, which I hope you realize by now, is not all that safe to use in this context.

2 Likes

Thanks a lot @rlkoshak !!!
That made the rule even more simple :slight_smile:

I wonder what is the best source for a list off all the methodes (API) I can use in my rules.
Partly the methods can be seen from Openhab.org documentation or from the cummunity posts like DesignPattern.

VS Code with the openHAB extension. Type in soemthing like the name of an Item and . to start a line of code and VS Code will bring up the list of all the valid ways to complete the statement.