Calling Lambda from Lambda

Rules DSL.
Openhab 4.1.2

At the root of my problem, I am trying to call a lambda from within a lambda (up to three calls deep). But, the lambda must be passed in as a parameter. What is the proper way to do this?

I thought I’d try to store the lambdas in a map, but this isn’t working. If this works, I’ll pass the lambdaCache in as a parameter.

val Map<String, Object> lambdaCache = newHashMap(
"lambdaStartExpire" -> val lambdaStartExpire = [ String zone |
    logInfo("lambdaStartExpire", "lambdaStartExpire")
    val expireSet = ScriptServiceUtil.getItemRegistry.getItem("hvacExpire_" + zone)
    val noexp = ScriptServiceUtil.getItemRegistry.getItem("noExpire")   // the inhibit switch for expiring
    if (noexp.state == OFF){
        expireSet.sendCommand(ON)
    }
    true
]
)

I solved the above problem, but what do I cast the object to to issue “apply()”?

(lambdaCache.get("lambdaStartExpire") as org.eclipse.xtext.xbase.lib.Functions$Function2<String, Map<String, Object>>).apply(zone, lambdaCache);

From a toString on a working lambda I see this

09:08:47.243 [INFO ] [el.script.Trigger from tstat updated] - Proxy for org.eclipse.xtext.xbase.lib.Functions$Function2: [param zone, param newSet | {
  logInfo(<XStringLiteralImpl>,<XStringLiteralImpl>)
  org.eclipse.xtext.xbase.impl.XIfExpressionImpl@489b452a (conditionalExpression: false)

Okay, here is what I believe to be working:

The rule

rule "Trigger from app setpoint changed"
when
    Member of gSetVirt received command 
then
    val String zone = triggeringItem.name.split("_").get(1) 
    (lambdaCache.get("lambdaSetByOccupied") as Function2<String, Map<String, Object>, Object>).apply(zone, lambdaCache);
end

The hashmap

val Map<String, Object> lambdaCache = newHashMap(
"lambdaStartExpire" -> [ String zone, Map<String, Object> lc |
    logInfo("lambdaStartExpire", "lambdaStartExpire")
    val expireSet = ScriptServiceUtil.getItemRegistry.getItem("hvacExpire_" + zone)
    val noexp = ScriptServiceUtil.getItemRegistry.getItem("noExpire")   // the inhibit switch for expiring
    if (noexp.state == OFF){
        expireSet.sendCommand(ON)
    }
    true
],
...

Don’t forget to import

import org.eclipse.xtext.xbase.lib.Functions$Function2 
import org.eclipse.xtext.xbase.lib.Functions$Function3

You need to move to another programming language. This is about as strong of a sign as there can be that you’ve outgrown what Rules DSL can provide for you. All of the other supported rules languages support functions and libraries.

Trying to work around the limitations of Rules DSL like this is only going to lead to brittle and hard to maintain code with tons of workarounds and stuff you have to remember (did that lambda take two arguments or three?).

I’ve been down this path with lambdas and Rules DSL and it leads only to misery. Your time would be better spent learning one of the other rules languages and coding this sort of stuff in that. JS Scripting and jRuby are both equally as easy to learn and code in as Rules DSL.

Given your examples, I’m not seeing any advantage to using lambdas for this in the first place. Maybe other rules will show why it makes sense but with what’s shown, you’d be better off making is so the rules do more to eliminate the need for the lambdas in the first place.

If you want to continue down this lambda path a bit more before you come to the inevitable conclusion that it’s not working for you, I’ve a few recommendations which can make it a little bit better.

  • If the last line of the lambda doesn’t return anything, it will be a Procedure instead of a Function. It’s probably better to allow that instead of trying to force it to become a Function by adding true as the last line of the lambda.

  • Use the cache instead of a global variable. See Rules | openHAB

  • You might be able to store the Class of the lambda in the map so at least you don’t need to hard code the Function2<String, Map<String, Object>, Object> everywhere you need to call the function. You can pull the type from the Class instead. But again, this is just jumping through hoops to deal with the fact that Rules DSL doesn’t support proper functions.

  • Don’t forget that Rules DSL has the callScript Action which can call a block of Rules DSL code in a .script file in the $OH_CONF/scriptsfolder. There are some limitations (no imports so you'll have to use Java classes' fully qualified names, no return values, no arguments). If you have some lambdas that meet these criteria,callScript(‘scriptname.script’)is a lot better than(lambdaCache.get(‘lambdaName’) as Function0.apply()`.

I agree completely about moving out of DSL. I successfully tried JRule two years ago, but can’t seem to get a fresh bundle install to work right.

I really needed to get the current system working, and trying to implement some features was going to be some non DRY code. I have it working, and it’s not too bad.

Thanks on the Procedure. I didn’t know that.

I couldn’t get the cache to work, but I’m not planning to spend any more time on DSL, and move to Java.

callScript() … hmmm, didn’t know about that either.

Thank you so much for the support you provide to this community.