Is there a **simple** lambda example?

OK, so I’m trying to make heads or tails of the lambda expressions wiki page, but kind of feel like it’s trying to teach fine points of grammar when I’m looking for more nouns-and-verbs level instruction.

Is there a simple lambda example?

Failing that, could someone explain some things about the example?

  • why is there a java map being used right at the beginning instead of an openhab timer? Is this sort of construct required to use a timer in any lambda? (…why?)
  • org.eclipse.xtext.xbase.lib.Functions$Function5 – why is this named this way? Does “Function5” mean that it takes five arguments? Or did the guy just like naming his functions “Function[ArbitraryNumber]”?
  • org.openhab.core.library.items – is there a list anywhere of what item types can (and cannot!) be passed into a lambda, and what they’re named?
  • Where do lambdas live? Do you put them in rules files? In scripts? Someplace else? Do they have to be in a certain place in those files, similarly to variables in regular rules files?
  • Are lambdas global? Can I define a lambda in one file and use it everywhere? Is there such a thing as a local lambda?
val org.eclipse.xtext.xbase.lib.*
// imports the Functions object. This is the base object of a lambda

val Functions$Functions Foo = [ String s |
    // a lambda is a global variable like any other. By using val as opposed to various we are saying 
    // that Foo is final and cannot be reassigned. If you try to have a line that starts with "Foo =" you
    // will get an error.

    // Next you will see Functions. This is the root object of a lambda. Then you have $ which 
    // is a way to reference a subclass of Functions. Finally there is Function1. This is the subclass
    // and the important part is the 1. This numbe must match the number of arguments the lambda
    // will accept. You can have up to 7.

    // The [ denotes the start of a lambda. You may have seen these square brackets before in
    // timers and forEach. Those are lambds too. The only thing we are doing differently here is
    // we are giving the lambda a name so it can be reused.

    // Immediately after the bracket you list your arguments. In this cas we have only one and it 
    // is a String.

    logInfo ("lambda", s)

    // All this lambda does is log the passed in argument. You can do almost anything in a 
    // lambda t b at you can do in a rule.however, the lambda has no context so if you need
    // to reference global vars or vals or rule local vars and vals you must pass them to the
    // lambda as an argument.
]

// The closing bracket denotes the end of the lambda 

...

    // somewhere  in a rule
    Foo.apply ("bar")

    // to call a lambda use its name followed by apply and the arguments in parens.

It doesn’t get any simpler than that I’m afraid.

The example creates a separate timer for each relay and stores them in a Map using the passed in String as the key. While it is a common way to keep track of a bunch of timers, it is not related to the lambda syntax. It is declared right at the beginning because it is a global val. But like I said above, the lambda doesn’t see globals so this Map is passed in as the third argument.

I mostly addressed this above. The key part is yes, the number at the end denotes the number of arguments.

Any object can be passed into a lambda. The class names follow a set pattern. Items are named TypeItem where Type is the Item Type you use in your .items file. E.g. SwitchItem, DimmerItem, NumberItem, GroupItem. If you don’t care that specifically, you can just use Item. Similarly states follow a similar pattern but instead use Type. E.g. DecimalType, OnOffType, etc.

The trick is, if you are using OH 1.x, figuring out the right import. Unfortunately these classes are scattered across several different packages. I’m on my poo hone right no so can’t easily look them up. I’ll try to come back tomorrow. In the mean time search “openhab SwitchType” and one of the hits will be the actual source code on github. The top line of actual code will start with “package”. That is what you need to import. OH 2 imports them for you.

A named lambda is a global val lIke any other. As such it must be at the top of a rules file before the actual rules. Only rules in that file will be able to call it.

They are global to to the file only, just like your other global vals and vars. You can and probably already have made local lambdas. Every time you create a timer or do a forEach or filter or other places where you have used square brackets with the | you have been creating a local lambda. You just haven’t been giving them a name so you can refuse them. But if you wanted you could do

val Functions$Function timerBody = [| logInfo ("timer", "bar")]
createTimer (now.plusSeconds(10), timerBody )
6 Likes

THANK YOU RICH!! In fact, double thank you! Would you mind if I copied this example to the wiki? Because the example there is like trying to learn how to rebuild an engine and the first direction is, “step 37, now you remove the camshaft”… i.e. you know there’s 36 steps beforehand, except there’s no mention of them anywhere!

(IMHO It’s worth keeping the step 37 example as a secondary, but it’s a horrendous first example.)

Sure. Just double check the grammar and wording. It is really hard to do that on my phone. I’ve been embarrassed many times looking back at past postings. Stupid auto-correct.

Well, trying to work through your example, but not getting very far despite it being a simple one. (Go figure.) Seems as though passing a String into a lambda doesn’t work under certain circumstances. In your example, the designer pukes on passing String because it calls it an incompatible type.

Interesting thing is the example in the wiki works as written, but if you change it to Functions$Function2 and delete everything passed before the String qualifier, then it too barfs with the same message.

So I think this is a casting problem, but have no idea how to solve it. A little help? Thanks!

Well I did just type it out on my phone. Hmmmm.

Yes, total typo in there. It should be Function1 as you discovered. I don’t think the New York Times will be hiring me as an editor anytime soon. Also, there is a little trick with lambdas that jump up and grab you sometimes and this is apparently one of those times.

Usually the error that pops up says something about the lambda not causing side effects. The incompatible types error is a new one on me. However, it went away when I used my usual trick for correcting that problem.

Here is a version that doesn’t barf in Designer (without the comments):

val Functions$Function1 foo = [ String s |
    logInfo("lambda", s)
    true
]

Whatever value is returned by the last line of a lambda will become that lambda’s return value. logInfo doesn’t return anything so if the lambda doesn’t cause any side effects and doesn’t have something to return the Rules DSL becomes very unhappy. So just putting a “true” as the last line will cause the lambda to always return “true” and make it happy again.

I totally forgot about this snag on my original posting.

1 Like

That got me fixed right up, thanks! I wasn’t going to hold your typos against you; typing replies on phones suuuuuuuuuuuuuuuuucks, even with most external keyboards. That’s actually why I copied your example with the comments, so I could correct things before stuffing them into the wiki. :wink:

One more question… is there a way to put a lambda inside another file I can include somehow? In other words, I’d like to use the same lambda inside several rules files. Is there any way to do that, besides cutting and pasting it around? (And the obvious sucky-ness that’d entail.)

No. :frowning: If your lambda doesn’t need anything passed to it beyond Items you can use a Script. But if you find you want to use a lambda across rule files:

  • Consider reorganizing your rules by function (e.g all lighting in one file). I find when organized this way the need for such cross cutting lambdas decreases if not completely disappears
  • Consider using a rule instead of a lambda. For example, I have a Notification_Info and Notification_Alarm String Items. When I want to send out a notification to me or my wife’s phone I just sendCommand to one of these two Items and a rule triggers which encapsulates the sending of the message. Having done this I can send alerts from any rules file and it is centrally processed. So today I use my.openhab and pushbullet, but I can swap that out in one place and the change will be transparent to the rest of the code. I can also add some additional logic, such as only send alerts at Night, only send Info messages to me, only send alerts to me at night, etc.

Hello,

this example seems good to teach how to use lambda functions. I tried this myself but I am struggeling to import the Function object.

When I write

val org.eclipse.xtext.xbase.lib.*

in my rules file I get:

`Multiple markers at this line

  • missing EOF at ‘.’
  • Type cannot be derived
  • The value of the local variable org is not used
  • Value must be initialized`

Have you got an idea what I am doing wrong?

Thanks a lot and best regards

import org.eclipse.xtext.xbase.lib.*

Another typo. Gah!

Hrm… unfortunately this is such a system-wide lambda that it’d basically collapse all my rules into one or two massive rule files. That would solidly be in the cure-worse-than-disease territory.

The rule idea is interesting, but unfortunately in my situation also not workable; I need to pass four strings (no sears guitar jokes!) into my lambda. I could split it into multiple lambdas and get it down to three strings, but I’d be two strings short at that point. Oh well, c’est la vie. Had to ask!

Yeah, that was the first one I hit but was saving it for updating the wiki. :wink:

I wouldn’t necessarily give up. Since they are all four Strings, could you merge them into one and parse the values back out in your rule using .split? Or you could have four Items that you populate individually and a trigger Item to trigger the rule.

I guess it depends on what you deem the greater evil, needing to encode/decode your values out of a larger String or copy/paste and maintenance of the same lambda across lots of files.

Obviously I have no real insight into your setup, but if I had such a lambda my spidy sense would be going off and I’d be thinking about a different approach. What does this cross cutting lambda do? Is there something that could be done with Groups, moving state to Items, or creating virtual/proxy Items that might reduce the number of rules that need the lambda or eliminate the need for the lambda? Does it make sense or is it possible to move the code into a Script instead (usually requires moving arguments into Items).

I can’t tell you how many times I’ve rewritten my rules when I come to situations lust like what you are describing. It has also driven me to learn more and more about how to solve problems in the Rules DSL in different ways, building up my toolbox of design patterns.

I’ve yet to regret the rewrites as every one has resulted in more concise, flexible, and easier to maintain code.

Those are both valid ways of going about it, and thinking about it I’ll probably go with populating individual string items and then kicking a virtual switch that triggers a rule. That has the added benefit of persisting values, which allows code to get a little bit more sloppy and still be OK. (Though that’s probably not actually a good thing, thinking about it!)

It’s [going to be] a lambda that formats popup messages and then sends them out via UDP to my TVs. So the four things (five, now that I think about it) that need to be passed are receiving UDP device, title, subtitle, main text, and image url.

Really by rights I should probably write it to be a binding the way I’m using it, but I’m not ready to climb that mountain just yet. :wink:

Moving all the strings to faux items would almost allow this to be a script, the only sticking point I can see is trying to figure out a way to pass which item I actually want the script to act upon. (i.e. “which TV?”)

I thoroughly detest debugging code then having the exact same bug re-surface later because the code I debugged was copied & pasted elsewhere wholesale. That’s why I’m looking for options. :wink:

Another typo. Gah!

Hello,

that was my first thought, too. I tried it with import. But then happen this:

`import org.eclipse.xtext.xbase.lib.*

val Functions$Functions1 Foo = [ String s |
logInfo (“lambda”, “test”)
true
]`

And I get this message:

`Multiple markers at this line

  • Couldn’t resolve reference to JvmType ‘Functions$Functions1’.
  • Incompatible types. Expected void but was (java.lang.String)=>boolean`

I have no idea what’s wrong. Makes me crazy :cry:

When I do this it’s the same:

val org.eclipse.xtext.xbase.lib.Functions$Functions1 Foo = [ String s | logInfo ("lambda", "test") true ]

You seem to have a typo in your lambda expression. It has to read val Functions$Function1 [...], and not val Functions$Functions1 [...] (notice the missing s at the end of “Functions”)!

That was it - thank you so much :grin:

Now finally I can start cleaning up my rules by moving reuseable code to functions.

Best
humarf

Sloppy can be good. The fewer error cases you have to check for the simpler and easier to understand your code will be.

Here is how I would do it.

You can “pass” the name of the Item it should work on as one of the String Items. Put all your TV Items into a Group and in your message rule you can get a reference to the TV Item by name using a filter on the Group.

val destination = gTVs.members.filter[tv|tv.name == Destination.state.toString].head

I’m admittedly a bit low on sleep (so that may be the true reason), but I’m afraid you completely lost me here.

On the bright side, I was able to re-write everything as items and rules, so I’m getting my code to be globally re-usable, though not in the originally intended way. :wink:

Let’s say your TV Item is names Den_TV and all of your TV Items are members of the gTVs group. Create another String Item that serves as an argument to your rule (same as your other four), we will call it Destination_TV.

// Send the name of the TV Item to the notification rules
Desitnation_TV.sendCommand(Den_TV.name)

// In your rule get access to the Destination_TV Item:
val dest = gTVs.members.filter[tv|tv.name == Destination_TV.state.toString].head