Functions as Parameters
While this section is not necessary to understand Expressway, the concepts of higher order functions are useful to know about.
In a lot of Expressway's code, you might notice that a lot of classes and functions take parameters of type Supplier
.
Supplier<T>
is a functional interface (discussed later in this article)
that has a single method,get()
, which returns a value of type T
.
T
can be any type, allowing us to specify the return type of the get
method when we create a Supplier
object,
instead of when we define the Supplier
interface.
This is useful because it allows us to pass functions as parameters to other functions,
which can be very powerful when you want to create more flexible and reusable code.
For example, to create an InitLoopCondAction
,
you need to pass a Condition
, aka a Supplier<Boolean>
that returns true
when the action needs to be repeated.
You might notice that in the examples in the Base Actions section,
we don't actually define a Condition
object when we create an InitLoopCondAction
,
we instead create a function that returns a Condition
object.
Lambda Functions
Lambda functions are a way to define functions without giving them a name, also known as anonymous functions. They are useful when you only need to use a function once, and don't want to define it elsewhere in your code.
For example, in the PIDFActionEx
class in the Base Actions section,
we define a function hasArrived
that returns true if the motor is within 50 encoder ticks of the target position.
The return type Condition
indicates that we are actually returning a function,
and not whether the motor has arrived or not at the time hasArrived
is called.
When defining hasArrived
, we use {}
to specify that we are defining a function to return,
and not returning the Boolean value of the expression inside the braces.
When creating the InitLoopCondAction
, we pass hasArrived
into the superclass constructor,
which is the constructor of the InitLoopCondAction
class.
Functional Interfaces
Functional interfaces are interfaces that have a single abstract method, which is why they are also known as Single Abstract Method (SAM) interfaces.
In Java, functional interfaces are used to define lambda functions, as the lambda function must match the signature of the single abstract method in the functional interface.
As mentioned before, Supplier<T>
is a functional interface with a single method, get()
.
This is why we can pass a lambda function that returns a Boolean
into the InitLoopCondAction
constructor,
as the lambda function matches the signature of the get
method in the Supplier
interface.
In Kotlin, functional interfaces are not explicitly defined, as Kotlin has first-class support for lambda functions. This means that you can pass a lambda function directly into a function that expects a functional interface, or a function that expects a lambda function.
However, to ensure compatibility with Java, Expressway uses Java's default functional interfaces,
such as Supplier
and Consumer
.
However, some parts of Expressway will either create a Kotlin functional interface,
or a typealias for a Java functional interface, to make the code more readable.
For example, Condition
is a typealias for Supplier<Boolean>
,
which allows us to not specify the type of the Supplier
when we create a Condition
object.
Higher Order Functions
Higher order functions are functions that take other functions as parameters, or return functions as results.
This is why we can pass a lambda function into the InitLoopCondAction
constructor,
as the InitLoopCondAction
constructor is a higher order function.
Higher order functions are useful when you want to create more flexible and reusable code.
Java does not have first-class support for higher order functions, which is why we need to use functional interfaces to define lambda functions.
Other Functional Interfaces
There are many other functional interfaces in Java, such as Function
, BiFunction
, Predicate
, and Consumer
.
The other two functional interfaces used in Expressway are BiFunction
and Consumer
.
BiFunction<T, U, R>
is a functional interface with a single method, apply(T t, U u) : R
,
that takes two parameters of type T
and U
, and returns a value of type R
.
It is the superclass of FeedforwardFun
, which is used in the PIDFController
class.
Consumer<T>
is a functional interface with a single method, accept(T t)
,
that takes a parameter of type T
, and does not return a value.
It used in the PIDToPoint class to allow the user to specify a function
that sets the powers of drivetrain motors based on the result of the PID controller.