-
Notifications
You must be signed in to change notification settings - Fork 9
Functions as Objects
Java, Kotlin and Python all allow you to create functions that are stored as objects or variables. This allows you to pass it around, store it and execute it later. In general, this concept is known as anonymous functions (since you create a function without giving it a name).
Just like regular variables and objects, these can be passed as arguments to other functions, giving us a means of, for example, defining what happens when a button is clicked in a GUI.
These special function objects have types (known as "functional interfaces" or just "interfaces"). The type basically specifies what arguments the function should take and what sort of value it should return. Therefore, if a method were to require an argument of one of these special types, it would require you to provide it with a function that meets those criteria.
There are three main interfaces used by JISA
to specify anonymous function types. These are:
-
Runnable
- Takes no arguments, returns nothing -
Predicate
- Takes one argument, returnstrue
orfalse
(ie aboolean
) -
Evaluable
- Takes a row of data as an argument and returns a number calculated from that data
Runnable
objects are used to hold on to a block of code to run at a later time, often triggered by an event like a button being clicked.
Predicate
objects are used to define filters and rules. They take an object and test it, thus defining whether to filter it out based on whether it returns true
or false
.
Evaluable
objects are used when relating to ResultTable
objects. Used to take individual rows of data from it (as Result
objects) and return a number calculated by using the data.
In Java, anonymous functions are defined by use of "lambda expressions". To write one you name the arguments inside brackets (...)
like you do when creating a normal function, and the code goes between curly braces {...}
as usual too. However, you need to put an arrow ->
between them like so:
(arg1, arg2) -> {
// Code goes here
}
If the function takes no arguments then you still write the brackets ()
:
() -> {
// Code goes here
}
If it should return a value, then just put a return
statement like you usually would:
(a, b, c) -> {
double d = a + b;
double e = c / d;
return d + e;
}
If it is possible to write your function entirely on one line, then you can skip the curly braces and the return
keyword like so:
(a, b, c) -> a + b + (c / (a + b))
Any value returned by this line of code is automatically used as the return value of the lambda.
If you have a regular function defined somewhere that matches the arguments and return type of the lambda you need to create, you can simply reference that function instead of writing out a lambda by use of the syntax: ClassName::methodName
. For instance, if we had the following:
public class SomeClass {
public static double calculate(double a, double b, double c) {
return a + b + (c / (a + b));
}
}
Then we could instead write:
SomeClass::calculate
Runnable
Adding a toolbar button to a Grid:
Grid grid = new Grid("Title");
grid.addToolbarButton("Click Me!", () -> {
System.out.println("Button clicked!");
GUI.infoAlert("The button was clicked!");
});
or by using a method reference:
public class Main {
static Grid grid = new Grid("Title");
public static void main(String[] args) {
grid.addToolbarButton("Click Me!", Main::onClick);
}
public static void onClick() {
System.out.println("Button clicked!");
GUI.infoAlert("The button was clicked!");
}
}
Predicate
Removing numbers from a list that are greater than 5.0 (using a single-line lambda):
List<Double> myList = ...;
...
// Will remove all elements that return true when given to the specified lambda
myList.removeIf( (v) -> v > 5.0 )
Evaluable
Plotting the absolute value of column 3 against column 2 (using single-line lambdas):
plot.watchList(
rTable,
(r) -> r.get(2), // x-axis
(r) -> Math.abs(r.get(3)) // y-axis
);
In Kotlin you can create one of these function objects by use of a lambda expression. In Kotlin these can be single-lined or multi-lined like so:
// Single-lined
{ arg1, arg2 -> /* Code goes here */ }
// Multi-Lined
{ arg1, arg2 ->
// Code goes here
// over multiple lines
}
Whichever type you use, the final line in the lambda is treated as a return
so if your lambda is to return a value you do not need to write the return
keyword:
// Multi-lined
{ a, b, c ->
var d = a + b
var e = c / d
d + e
}
// Single-lined
{ a, b, c -> a + b + (c / (a + b)) }
In the case of your lambda having no arguments, you can omit the ->
entirely:
// Single-lined
{ println("Hello World!") }
// Multi-lined
{
println("Hello World!")
println("Goodbye World!")
}
If your lambda has only one argument, you can omit the argument name and ->
, instead referring to it as it
:
{ x -> 50.0 * x + 25.0 }
// can be replaced with
{ 50.0 * it + 25.0 }
Just like with Java, if you have a method defined somewhere that matches the lambda that you need to create, you can simply reference that method instead by writing its name without ()
and with ::
before it:
fun main() {
var lambda = ::doSomething
}
fun doSomething() {
println("Something!");
}
If you want to reference a function inside an object or class just put the object or class name before the ::
.
Finally, if you are supplying a lambda expression as an argument to a function, and it is the last argument of that function, you can specify it outside the (...)
of the function, or of it's the only argument you can omit the brackets all together:
someFunction("Arg1", "Arg2", {
println("Helo World!")
})
// Can be written as
someFunction("Arg1", "Arg2") {
println("Hello World!")
}
// Only argument
otherFunction({
println("Hello World!")
})
// Can be written as
otherFunction {
println("Hello World!")
}
Runnable
Adding a toolbar button to a Grid
:
val grid = Grid("Title")
grid.addToolbarButton("Click Me!") {
println("Button clicked!")
GUI.infoAlert("The button was clicked!")
}
or by using a method reference:
val grid = Grid("Title")
fun main() {
grid.addToolbarButton("Click Me!", ::onClick)
}
fun onClick() {
println("Button clicked!")
GUI.infoAlert("The button was clicked!")
}
Predicate
Removing numbers from a list that are greater than 5.0 (using a single-line lambda):
var myList: List<Double> = ...
...
// Will remove all elements that return true when given to the specified lambda
myList.removeIf { it > 5.0 }
Evaluable
Plotting the absolute value of column 3 against column 2 (using single-line lambdas):
plot.watchList(
rTable,
{ it.get(2) }, // x-axis
{ abs(it.get(3)) } // y-axis
)
In Python you can create an anonymous function by use of a lambda expression just like Java and Kotlin. However, Python lambdas can only be on a single line:
anon = lambda arg1, arg2 : # Code goes here #
They will automatically return whatever is returned by the line of code:
anon = lambda a, b, c: a + b + (c / (a + b))
anon(1, 2, 3) # Will return 4
If you want to use more line, you will need to create a function with the required arguments and reference it by providing its name without the ()
like so:
def myMethod(arg1, arg2):
# Do something with arg1 and arg2
# Anything at all
anon = myMethod
Runnable
Adding a toolbar button to a Grid
:
def onClick():
print("Button clicked!")
GUI.infoAlert("The button was clicked!")
grid = Grid("Title")
grid.addToolbarButton("Click Me!", onClick)
Predicate
Removing all numbers from a list that are greater than 5.0:
list = ...
# Removes all elements that return true when given to the lambda
list.removeIf( lambda v: v > 5.0 )
Plotting the absolute value of column 3 against column 2 (using single-line lambdas):
Evaluable
plot.watchList(
rTable,
lambda r: r.get(2), # x-axis
lambda r: abs(r.get(3)) # y-axis
)
- Getting Started
- Object Orientation
- Choosing a Language
- Using JISA in Java
- Using JISA in Python
- Using JISA in Kotlin
- Exceptions
- Functions as Objects
- Instrument Basics
- SMUs
- Thermometers (and old TCs)
- PID and Temperature Controllers
- Lock-Ins
- Power Supplies
- Pre-Amplifiers
- Writing New Drivers