Teaching New Command Framework and Lambdas

Hello,
I have come up against an interesting little challenge. Our team is learning Java, and though we are doing relatively well in understanding Timed Robot, because I want to teach the students some more advanced techniques, I want to teach the new Commands Framework.

My stumbling block is that many of the New Commands framework implement Lambdas. Specifically, many of the examples I see for the Romi (which we are using as a platform) use suppliers rather than directly manipulating things in the method itself.

I am not an incredibly advanced programmer myself, and as such, it is difficult for me to explain how Lambdas work and why we use them instead of invoking the method as normal.

I was wondering, can anyone could help me explain this to the team or point me to some resources that could help me grasp an understanding beyond my current (probably partially proficient understanding) of how they work?

Lamdas are a shorthand way of invoking a method.
a Supplier is a functional interface that streamlines passing objects to and from a lambda or method.

3 Likes

It might be worth explaining the alternative before jumping in to lambdas. You can pass a Command object for button bindings. Give a subsystem with an internal boolean, write two commands to toggle the internal state. You could imagine a EnableBooleanCommand and a DisableBooleanCommand that take a subsystem and toggle that subsystem state.

The alternative method takes a Runnable, which are similar to the Supplier interfaces you mentioned, but takes no arguments and has no return type. You could imagine writing two methods in the subsystem class to toggle the internal boolean that takes no arguments - enableBoolean and disableBoolean - which you could pass as a Runnable to the button bindings using the :: operator in Java. Instead of passing the commands, you’d pass a subsystem::enableBoolean and subsystem::disableBoolean to your whenPressed functions.

Finally, imagine how you could clean up that code. What if you could call a setBoolean function and pass it a true/false. This won’t work with our Runnable, since our Runnable cannot have parameters. We can define an inline function (an anonymous function, called a lambda in Java) that takes no arguments but calls our setBoolean function with a true/false. Passing a - () -> setBoolean(true) and () -> setBoolean(false) to the whenPressed functions. These lambdas can be thought of like our previous functions. They take no arguments and have no return - they’re just inline as opposed to living in a different file.

Hopefully that tracks. That would be the way I would lead students through why to use lambdas when defining commands. I can provide some code snippets as well if the plain-text explanation doesn’t make sense, or is too abstract.

Edit: Here’s some code samples for each step. Hopefully the code will help illustrate the plain-text answer

5 Likes

To add onto the above reply, lets walk through some of the history of how we got to using lambdas, using PID controller. Its not exactly commands, but it will explain how we got there, because old commands to new commands went through the same iterations mostly.

A PID Controller is fairly simple. Lets just start with a simple position PID controller. At a high level, this controller takes an encoder value as input, and gives a speed as output.

If we start with classical OOP, we would handle this using interfaces. We would have PIDInput and PIDOutput interfaces, likely with the folloing definitions.

public interface PIDInput { double getPidInput(); }
public interface PIDOutput { void setPidOutput(double value); }

Your encoders would implement PIDInput, and your motors would implement PIDOutput. You could then pass your encoder and motor as objects to the PID controller, and then everything works. The PID controller would call getPidInput on the input, and setPidOutput() on the output. Everything is great!

Except, lets start putting some edge cases in. An encoder has 2 types out outputs, a velocity output and a position output. Which one of these do you return when getPidInput is called? You can change your interface to a getPidPosition() and getPidVelocity(), but then which one do you call from your PID controller? You need to either make the PID Controller interface and code more complicated, or make 2 different implementations for PID Controller that call the 2 different methods. Or you have 2 different input interfaces, and then still need multiple PID Controller. Note the logic for the PID Controller is the same in both cases, just how it gets the input is different.

What if you want to pass the output to 2 motors? You then need to make a wrapper class that takes 2 PIDOutputs, and implements PIDOutput to call setPidOutput() on both of those motors. We also have the same encoder problem on the output, where the controller could output multiple different units, either percentage, voltage or maybe even current.

Finally, what happens if a vendor wants to use getPidInput or setPidOutput to mean something different. Theyre basically forced to follow our naming structure, which might not match what they have.

With all of this, classical OOP is actually pretty constricting, and lots of edge cases to deal with. So instead of thinking in objects, lets think in functions and data.

A PID Controller needs an input number, and gives an output number. What units these number are in don’t matter to the controller. So at the input, lets take a DoubleSupplier. This is an interface built into Java that takes nothing andreturns a double. As an output, lets take a DoubleConsumer, which takes a double and returns nothing. Now I could make my encoder implement DoubleSupplier and my motor implement DoubleConsumer, but thats just back at square 1, so we’re not going to do that.

Instead, both of these interfaces are marked as FunctionalInterface. This means they have just a single function in them. This means we can pass any function that matches the signature. So we could find a get function on our encoder and a set function on our motor, and pass those to the controller using method references, as explained above.

But what if we don’t have a matching signature on one or both of our motors/encoders, or if we want to do a multi motor output? Theres 3 ways we can make this happen.

  1. Add an instance or static function somewhere that matches the signature, and have this function do the mapping of data.
  2. Implement the function using an anonymous class. Don’t do this anymore, unless you have a reason #3 doesn’t work (I have to deal with this a lot in the Gradle tooling we provide, but robot code this is never the case).
  3. Use a lambda function. This allows us to create a function inline. Most of the times, the mapping we’re doing to do is simple, potentially something like getEncoderDistance() * 3.14. Instead of having to write a whole bunch of boilerplate code to make this work, we just write a quick lambda that does the mapping in a single line. And on the output side, if we need to call set on 2 motors, we can do so in just 2 lines.

Of these 3 options, the lambda both has the simplest syntax and is the most expressive, as whats happening happens directly at the call site, rather then having to dig around the code.

Once you have this, your PID Controller can work with pretty much any data, and its up to the user to pass the right data in and do the right thing with the output data. From a library side, this is much simpler, much less complicated, and a lot more powerful as well.

When first starting with lambdas, they can seem complicated, but the big key to them is realistically they’re just a way of passing a function around with a simple syntax. Its fairly uniform, and at least in Java there are not a lot of footguns.

12 Likes

Thank you both. This is helpful and exactly what I was looking for. These are incredibly detailed examples that make a lot of sense and clearly explain how these pieces work.

1 Like