Functions
Mapping from a set of inputs X (domain) to a set of outputs (range) within a co-domain (Y). Every input in domain maps to exactly one output. Multiple inputs can map to the same output.

Pure Function
Takes in arguments and returns a deterministic value, effect-free. Absence of side-effects necessary for referential transparency. Deterministic also means not infinite / undefined.
Side effects: Anything a function does besides computing a returning a value. It’s an interaction with the outside world. Examples: modifying external state, program input/output, throwing exceptions.
Not deterministic
int q(int x, int y) {
return x / y;
}
// What happens y = 0
int s(int i) {
return this.x + i;
}
// x can be changed. This is not a function. Same input, different output.
Higher-order Functions
Functions are “first-class citizens” = functions can be stored in variables. Takes in other functions as arguments, returns other functions. E.g. reduce(0, (x, y) → x + f.apply(y));
Function with multiple arguments
Multiple arguments can be applied at the same time. Lambda expressions can be curried for partial application. f = x → y → x + y. f.apply(1).apply(2) returns 3.
Partial Application - Under the Hood
f.apply(1) takes x = 1 and returns y → 1 + y. This is partial application. Applying one argument and get back a function.
Function<Integer, Function<Integer, Integer>> f = new Function<>() {
@Override
public Function<Integer, Integer> apply(Integer x) {
return new Function<Integer, Integer>() {
@Override
public Integer apply(Integer y) {
return x + y;
}
};
}
}
Closure: Instance of Local Class
class A {
private final int z;
A(int z) {
this.z = z;
}
Predicate<Integer> foo(int y) {
return new Predicate<Integer>() {
@Override
public boolean test(Integer x) {
return x == y + z;
}
};
}
}
y is captured by value (e.g foo(2))
If I do foo(3), I'll create a new predicate.
I can also do pred1 = a.foo(2) and pred2 = a.foo(2) but they are separate objects.
When foo(2) returns and stack frame disappears, y = 2 lives on safely inside the Predicate object.
Even though you never wrote private final int y in the anonymous class, the compiler secretly generates it for you.
That's the magic of closures.
z is captured by reference
Variable capture
The closure has a copy of y and z, not the original. y and z cannot be changed and should be kept as final.

Function Composition
Function composition: (g ◦ f)(x) = g(f(x)) This reads as g compose f, which is the same as f and then g.
g.compose(f).apply("abc")
// g.compose(f) essentially means:
// input -> g(f(input))
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
return (V v) -> this.apply(before.apply(v));
}
f.andThen(g).apply("abc")
// f.andThen(g) essentially means:
// input -> g(f(input))
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
return (T t) -> after.apply(this.apply(t));
}
Func abstract class
abstract class Func {
abstract int apply(int x);
}
Func f = new Func() {
int apply(int x)
return x + 2;
}
}
Func g = new Func() {
int apply(int x)
return x / 2;
}
}
Example: Integer Function Composition
abstract class Func {
abstract int apply(int x);
Func compose(Func before) {
return new Func() {
int apply(int x) {
int result = before.apply(x);
return ???
}
};
}
}
Walking through g.compose(f).apply(10):
1) g.compose(f) returns a new Func object
2) .apply(10) is called on that new object
3) Inside: result = before.apply(10) → calls f(10) → 20
4) Then: Func.this.apply(20) → calls g(20) → 22
Generic Function Composition
abstract class Func<T, R> {
<V> Func<V, R> compose(Func<? super V, ? extends T> before) {
return new Func<V, R>() {
R apply(V v) {
return Func.this.apply(before.apply(v));
}
};
}
abstract R apply(T t);
}
The key idea: f.compose(g) creates a new Func<V, R> that does V → g → T → f → R. The before parameter uses PECS — ? super V (consumes V as input) and ? extends T (produces T as output) — so the composed function accepts any g whose input is a supertype of V and whose output is a subtype of T.
f.compose(g) g input (V) → output (T) for f f input (T) → output (R)
Type Parameters
Class-level parameters
Func<String, Integer> f = new Func<String, Integer>() {
Integer apply(String x) { return x.length(); }
}
// T = String (Input)
// R = Integer (Output) — locked in for this object
Method-level parameters
// map has a method-level type parameter U
<U> Maybe<U> map(Function<T, U> mapper)
Maybe<String> name = Maybe.of("Elkan"); // T = String, locked in
name.map(s -> s.length())
// Java sees the lambda returns an Integer
// So U = Integer, decided right now
// Returns Maybe<Integer>
Lazy vs Eager Evaluation using andThen
Eager
Stream.<String>of("one", "two", "three")
.map(x -> x.length()) // Stream<Integer>: 3, 3, 5
.reduce(0, (x,y) -> x + y) // 0 + 3 + 3 + 5 = 11
Lazy
Function<String, Function<Integer, Integer>> mapper = x -> y -> y + x.length();
Function<Integer, Integer> f = Stream.<String>of("one", "two", "three")
.map(mapper) // returns a function y -> y + x.length()
.reduce(Function.<Integer>identity(), (a, b) -> a.andThen(b));
f.apply(0)
So after .map(mapper), the stream becomes: “one” → y → y + 3 “two” → y → y + 3 “three” → y → y + 5
Now we reduce these three functions into one using a.andThen(b) — which chains them together. The identity Function.<Integer>identity() is the starting point (does nothing, just returns its input).
The reduction builds: identity.andThen(y → y + 3).andThen(y → y + 3).andThen(y → y + 5) This gives us a single Function<Integer, Integer> called f. Nothing has been computed yet — that’s the “lazy” part.