Lec 08 - Functional Programming
Mathematical Functions
In maths, we have learned that function refers to a mapping from domain to codomain. Every input in the domain must map to exactly one output but multiple inputs can map to the same output.
Basically, mathematical functions have the following two advantages:
No side effects: This means given a function , applying it on , a.k.a doesn't change the value of or any other unknowns etc. It simply computes and returns the value.
Referential transparency: This means if we let , then in every formula that appears in, we can safely replace occurances of with . We can be guaranteed that the resulting formulas are still equivalent.
Pure Functions
Ideally, functions in our program should behave the same as functions in mathematics. This requires us to treat functions as first-class citizen in our program, which means we can assign functions to a variable, pass it as arguments, return a function from another function, etc, just like any other variable.
Such a counterpart of mathematical functions in our program is called pure functions. This means that the pure functions also have the following two properties:
No side effects: This means our function does not
print to the screen
write to files
throw exceptions
change other variables
modify the values of the arguments
Deterministic: This means that given the same input, the function must produce the same output, every single time. This deterministic property ensures referential transparency.
Functional Programming
We refer to the style of programming where we build a program from pure functions as functional programming (FP).
Functions as First-class Citizen
We have already seen what this means from above. Now, let's recap an example where we pass a function as an argument.
In this code, the comparison function cmp here is implemented as a method in an anonymous class that implements an interface. We can think of an instance of this anonymous class as the function. Since a function is now just an instance of an object in Java, we can pass it around, return it from a function, and assign it to a variable, just like any other reference type.
Lambda Expression
While we have achieved functions as first-class citizens in Java, the code is verbose and ugly. Fortunately, there is a much cleaner syntax to write functions that applies to interfaces with a single abstract method — that's lambda expression.
A lambda expression is a shorthand syntax for implementing a functional interface (an interface with a single abstract method) in Java. It defines a function inline, specifying its parameters and body, without requiring a full method declaration or class definition.
Such an interface with exactly one abstract method is called a functional interface. And a key advantage for a functional interface is that there is no ambiguity about which method is being overridden by an implementing subclass.
An example is as follows, notice that the Lambda Writing can be achieved by removing the unnecessary part from the anonymous class writing!
So, the expression above x -> x * x is called lambda expression.
The LHS lists the parameters (use
()if there is no parameter)The RHS is the computation.
If RHS only has one line, you can omit curly
{}braces andreturn.If RHS has multiple lines, use curly braces
{}and explicitly writereturnif a value is returned.
A lambda expression is essentially syntactic sugar for writing an anonymous class that implements a functional interface.
Method Referencing
A lambda expression is useful for specifying a new anonymous method. Sometimes, we want to use an existing method as a first-class citizen instead. That is why it comes method referencing.
An example using the distanceTo(Point p) is shown as follows,
The double-colon notation :: is used to specify a method reference. We can use method references to refer to:
static methods in a class
Box::ofinstance method of a class or interface
x::compareToconstructor of a class
Box::new
However, in Line 4, we should be extremely careful because if there are multiple matches or if there is ambiguity in which method matches, the Java compiler will generate a compile error!
Curried Functions
Mathematically, a function takes in only one value and returns one value. In programming, however, we may write functions that take in more than one value. In FP, this can be achieved by using curried functions.
Currying is the technique that translates a general n-ary function to a sequence of n unary functions.
This utilizes the fact from the first-class citizen principle that functions can be returned from a function!
Lazy Evaluation
The Lazy evaluation means that we only invoke the function when we truly needs to do so. This can be done in two ways:
Use Lambda as Delayed Data
Memoization
Lambda as Delayed Data
Recall that when we write a lambda expression as follows:
We are just defining function . To invoke the function , we need an argument for x. So, nothing will happen here because we are just defining the function.
So, we can pass in the lambda expression to procrastinate our computation until we really need the data. For the example, please see from the lecture notes.
Memoization
This is the same as what we haved encountered in CS1010 or some other data structures. So, the basic idea here is that if we have computed the value of a function before, we can cache (or memoize) the value, and keep it somewhere, so that we don't need to compute it again.
Last updated