aliases:
- Functional Interface
- Lambda
Lambdas
A "Lambda" is also known as an "anonymous function" . In a general sense, lambda's are just a quirky syntax to create "on the fly" functions, and otherwise work basically like any other function.
In WPILib code, it's common to want to pass procedures and code blocks to Commands and Triggers, allowing those functions to know "how to get new values", rather than just what the values are when you first try to set them up. This is critical for real-time decisions, like "is a game piece loaded?" which you won't know when creating the command.
WPILib has good further reading that might assist with understanding.
https://docs.wpilib.org/en/stable/docs/software/basic-programming/functions-as-data.html
Functions should be common to most programmers. Let's take a look at the obvious properties of a function to make sure we're well set.
For something like
public void sayHello(String name){print("hello "+name);};
we can see that
sayHello
name
void
, or has no return value. However, another sneaky property is the function's scope. Scope represents variables and functions that the function has access to that are not strictly part of the function itself. For Java, this often indicates files in the same class. This too should look familiar:
public class ExampleClass{
String greeting="sup ";
public void sayHello(String name){print(greeting+name);};
}
Here, greeting
is part of sayHello
's scope, but not part of the function itself. This is a property of the function, even if it's not an apparent one.
This example takes uses Commands.run()
, which takes a Runnable
object, and executes once per robot loop. A Runnable
simply describes "a function that takes no argument, and returns no values". We'll come back to this.
We can pass this to a command in two different ways
//Create a named function like we're used to
// and then pass a reference to it
void printSomething(){System.out.println("something");}
Commands.run(this::printSomething);
//Or, just create it directly.
Commands.run(()->{System.out.println("something");});
}
We can see that a lambda looks very similar to a function, with effectively the same structure.
// return type | function name | parameter list | the code block
void printSomething () {System.out.println("something");}
() -> {System.out.println("something");}
The primary difference is the missing function name, and lack of explicit return type. We don't need a name because it's anonymous. We don't need a return type, because it's declared by the Command's argument type: Runnable
.
The main difference is the ->
; This is just the special lambda syntax. It might help to consider it as "goes to" or "yields" ; as in the parameter list (...)
yields a value {block of code}
.
Just as "Runnable" defines "A function that accepts no arguments and returns nothing", there's a whole bunch of standard references for functions that "Take X" and "Return X".
These are generally broken down into Suppliers (returns a value) and Consumers (accepts a value). Here's a few.
Runnable // takes nothing, and returns nothing. Just a pure code block.
BooleanSupplier // A function that takes nothing, and returns a boolean
DoubleSupplier // A function that accepts nothing, and returns a double
BooleanConsumer // Function that takes a boolean and returns nothing.
That's about it! These helper classes
A great reference is the FunctionalCommand
class. This class takes 4 lambdas, replacing each of the core parts of a Command with a simple code. It looks like this.
FunctionalCommand(
Runnable, //initialize
Runnable, //execute
BooleanSupplier, //isFinished ; returns a boolean
BooleanConsumer // end; takes a boolean that indicates if it was cancelled.
);
Let's say we want to create a command that
double startTime=0;
new FunctionalCommand(
()->{//initialize by recording the time
startTime=Timer.getFPGATimestamp();
System.out.println(startTime);
},
()->{}, //execute: do nothing
()->{return Timer.getFPGATimestamp()-startTime>=5}, //isFinished
(cancelled)->{ //our end block
System.out.println(Timer.getFPGATimestamp()-startTime);
System.out.println(cancelled);
}
Hooray! This works, and leads us to a useful confirmation: Like any other function, you can access any variable in the current scope in which it was created.
When we combine this feature with Subsystems and the Factory pattern for commands, we can have "function scope" variables to serve as data between the components of the command.
This scope access also is a large value add when creating Commands inside of Subsystems. We can just access useful constants, motors, encoders, and helper functions without any extra setup.
It's often helpful to take functions as arguments. This allows you to isolate the code you're writing from objects that create that data.
Let's create a function that takes two lambdas, and returns the value of the larger one. You
Double myFunction(DoubleSupplier left, DoubleSupplier right){
var letfvalue = left.getAsDouble();
var rightvalue = right.getAsDouble();
if(letfvalue > rightvalue) return left;
return right;
}
This command doesn't do much in practice, but demonstrates the process. A more utilitarian practice is something like a "Drive" command on the robot; Sure, you could take a Joystick object, and then read from that. But, what if you want to feed it values from a Vision process? Vision doesn't have a joystick, so, you need a new entire Drive command for it. If you want to use a rangefinder? Another whole drive command.
Instead, creating a DoubleSupplier interface means your drive command can get values from arbitrary functions, making them much more generic and helpful.