Understanding the Building Blocks of FP
Dervis Mansuroglu
Functional Programming
In Java 22
JavaDay Istanbul 2024



Principal Officer, developer, 15 years of exp.
Leader of the Norwegian JUG, javaBin

Who am I and why listen to me?

Oslo Software Architecture Co-Organizer
Java Champion

A Brief Summary
How Java is changing

Java is about clear, readable and reliable code.
Writing as less code as possible is not a goal itself for the Java programming language.

Make it easier to build and maintain reliable code ... that's why people continue to use and trust Java.
- Brian Goetz
Java adopts concepts from functional languages.
JEP-395 Records
JEP-360 Sealed Types
JEP-361 Switch Expressions
JEP-394 Pattern Matching, instanceof
JEP-441 Pattern Matching, switch
JEP-440 Record Patterns
JEP-443 Unnamed Patterns & Variables


Deconstructional pattern matching like in
Ref: https://javaalmanac.io/features/
Future features that enhances functional style in Java
JEP-468 Derived Record Creation
JEP-401 Value Classes
JEP-??? Null-Restricted Value Class Types

Ref: https://javaalmanac.io/features/
Important differences
Everything is functions.
Everything.
Lazy evaluation
Expression evaluation is delayed until needed. Its value remains constant between different points in time.
Immutability
An expression can be replaced with its value without changing the program's behavior.
Java's journey
Important changes in Java 7-22
Java's Generics
Introduced in Java 5 to allow generic types.
int[] arr = new int[]{ ... };
List<Integer> arr = new ArrayList<Integer>();class Vehicle {}
class Car extends Vehicle {}
class Bus extends Vehicle {}
Vehicle[] busArray = new Bus[10];
busArray[0] = new Car(); // compiles :-(
List<Vehicle> list = new ArrayList<Bus>(); // errorJava's Diamond Operator
Introduced in Java 7 to allow type inference on generic types.
class Vehicle {}
class Car extends Vehicle {}
class Bus extends Vehicle {}
List<Vehicle> list = new ArrayList<>();Java's var keyword
Introduced in Java 10 to allow type inference on local variables.
public void makeList() {
List<Vehicle> list = new ArrayList<>();
var list2 = new ArrayList<Vehicle>();
}Java's intersection types with var
Introduced in Java 10 to allow type inference of multiple mix-in interfaces
var guest = (Welcome & Goodbye) () -> "World";
System.out.println(guest.welcome());
System.out.println(guest.goodbye());
>> Hello World
>> Goodbye Worldinterface Welcome extends Supplier<String> {
default String welcome() {
return String.format("Hello %s", get());
}
}
interface Goodbye extends Supplier<String> {
default String goodbye() {
return String.format("Goodbye %s", get());
}
}Ref: https://javaalmanac.io/features/var/
Streams API & functions
Introduced in Java 8
Anno 2014
Functional Interfaces
- Function<T,R>
- BiFunction<T, U,R>
- Predicate<T>
- Supplier<T>
- Consumer<T>
- UnaryOperator<T> extends Function<T, T>
- BinaryOperator<T> extends BiFunction<T,T,T>
- BiPredicate<T>
- BiConsumer<T>
Function<Integer, Integer> f;
f = x -> x + 1;private static Integer lambda$new$0(Integer);
0: aload_0
// Integer.intValue:()I
1: invokevirtual #24
4: iconst_1
5: iadd
// Integer.valueOf:(I)L/Integer;
6: invokestatic #30
9: areturn
Issues
- ADT's are not part of the language nor the library
- Was built before pattern matching, sealed interfaces, exhaustiveness and unnamed pattern variables.
- Certain function types do not build upon each other
- Example: Function vs BiFunction
- Example: Function vs BiFunction
- Java Stream pipeline api lacks intermediate functions
- Only extension point is custom Collectors
18.03.2014
19.09.2023
- Record Patterns (JEP 440, Java Almanac)
- Pattern Matching for switch (JEP 441)
- Unnamed Patterns and Variables (1st, JEP 443)
- Unnamed Classes and Instance Main Methods 1st, JEP 445)
15.03.2024
- Stream Gatherers 1. Preview (JEP 461)
19.09.2023
- Record Patterns (JEP 440, Java Almanac)
- Pattern Matching for switch (JEP 441)
- Unnamed Patterns and Variables (1st, JEP 443)
- Unnamed Classes and Instance Main Methods 1st, JEP 445)
15.03.2024
- Stream Gatherers 1. Preview (JEP 461)
18.03.2014
Aka ADT's
Algebraic Data Types
Sum type
This OR That.
Product type
This AND That
https://en.wikipedia.org/wiki/Tagged_union
Java records
Haskell data record types
Java classes
C structs
Tuples
https://en.wikipedia.org/wiki/Product_type
Java enums
Haskell data types (several constructors)
Also called: variant, choice type, disjoint union, coproduct
Sum type
This OR That.
Product type
This AND That
Records
Sealed classes & interfaces
Exhaustive Pattern Matching
Destructuring / Decomposition
Haskell
data Animal = Dog | Cat | Elkdata Animal = Dog String
| Cat String
| Elk String
name (Dog n) = n
name (Cat n) = n
name (Elk n) = nHaskell
data Animal = Dog String
| Cat String Int
| Elk Int String Double
| Unknown
info (Dog n) = "Dog: " ++ n
info (Cat n _) = "Cat: " ++ n
info (Elk _ n _) = "Elk: " ++ n
info _ = "Unknown animal"Haskell -
data Group = Mammal | Bird | Fish deriving (Show)
data Animal = Animal {
name :: String,
age :: Int,
group :: Group
}
elephant = (Animal "Elephant" 10 Mammal)
elephantName = name a
elephantAge = age a
elephantGroup = group aRecords
ADT's are patterns
Represents variants of data
A function can return either an error, or a result
A GPS-coordinate has a latitude and a longitude
A chess piece can be one of pawn, knight, bishop, rook etc.
A function might return a result or nothing
Maybe a = Just a | Nothing
Avoid null values. Alternative for Optional
Either a b = Left a | Right b
This or That (disjunction). Can be used to handle exceptions
These a b c = A | B | Both
This or That or Both. Choice type
Try a = Success a | Failure a
Functionally catch exceptions
ADT's with Java
Sealed types + Records
public sealed interface Maybe<T> permits Maybe.Just, Maybe.Nothing {
record Just<T>(T t) implements Maybe<T> {}
record Nothing<T>() implements Maybe<T> {}
static <T> Maybe<T> ofNullable(T value) {
return value != null ? new Just<>(value) : new Nothing<>();
}
default boolean isNothing() {
return this instanceof Maybe.Nothing<T>;
}
default boolean isJust() {
return this instanceof Maybe.Just<T>;
}
default <X> X maybe(Supplier<X> leftFn, Function<T, X> rightFn) {
return switch (this) {
case Nothing<T> _ -> leftFn.get();
case Just<T> left -> rightFn.apply(left.t());
};
}
}This is only an approximation, not a full monad
Records
Immutable data classes with named accessors
Project Amber
Java -
enum Group { Mammal, Bird, Fish }
record Animal(String name, int age, Group group) {}
var elephant = new Animal("Elephant", 10, Mammal);
String name = elephant.name();
int age = elephant.age();
Type group = elephant.group();Records
Sealed Types
A step closer to ADT's
Project Amber
public sealed interface Animal permits Cat, Dog {}
public record Cat() implements Animal {}
public record Dog() implements Animal {}Sealed interface
public sealed abstract class Animal {}
public final class Cat extends Animal {}
public final class Dog extends Animal {}Sealed class
public sealed interface Animal {
record Cat() implements Animal {}
record Dog() implements Animal {}
}Sealed interface

public sealed interface Animal permits Cat, Dog {}
public record Cat(String name, int age) implements Animal {}
public record Dog(String name, int age) implements Animal {}
String afterJDK19(Animal animal) {
return switch (animal) {
case Dog d when d.age > 10 -> "Old dog: " + d.name();
case Dog d when d.age < 2 -> "Young dog: " + d.name();
case Dog d -> d.name();
case Cat c -> c.name();
};
}public sealed interface Animal permits Cat, Dog {}
public record Cat(String name, int age) implements Animal {}
public record Dog(String name, int age) implements Animal {}
String beforeJDK19(Animal animal) {
return switch (animal) {
case (Dog d && d.age > 10) -> "Old dog: " + d.name();
case (Dog d && d.age < 2) -> "Young dog: " + d.name();
case Dog d -> d.name();
case Cat c -> c.name();
};
}public sealed interface Animal permits Cat, Dog {}
public record Cat(String name, int age) implements Animal {}
public record Dog(String name, int age) implements Animal {}
String fromJDK21(Animal animal) {
return switch (animal) {
case Cat(var n, var a) -> "Cat name = %s, age = %s".formatted(n, a);
case Dog(var n, var a) -> "Dog name = %s, age = %s".formatted(n, a);
case null -> "Invalid animal";
};
}
// Record Patternspublic sealed interface Animal permits Cat, Dog {}
public record Cat(String name, int age) implements Animal {}
public record Dog(String name, int age) implements Animal {}
String fromJDK21(Animal animal) {
return switch (animal) {
case Cat(var _, var a) -> "Cat age %s".formatted(a);
case Dog(var n, _) when n.isEmpty() -> "Dog without name";
case Dog(_, var a) when a > 10 -> "An old Dog";
case Dog(_, _) -> "Unknown Dog";
case null -> "Invalid animal";
};
}
// Unnamed patterns and variables public sealed interface Either<L, R> permits Either.Left, Either.Right {
record Left<L, R>(L l) implements Either<L, R> { }
record Right<L, R>(R r) implements Either<L, R> { }
static <L, R> Left<L, R> left(L l) {
return new Left<>(l);
}
static <L, R> Right<L, R> right(R r) {
return new Right<>(r);
}
default boolean isRight() {
return this instanceof Either.Right<?, R>;
}
default L left() { return ((Left<L, ?>)this).l(); }
default R right() { return ((Right<?, R>)this).r(); }
default <X> X either(Function<L, X> leftFn, Function<R, X> rightFn) {
return switch (this) {
case Either.Left<L, ?> left -> leftFn.apply(left.l());
case Either.Right<?, R> right -> rightFn.apply(right.r());
};
}
}This is only an approximation, not a full monad
Terminology in FP
A short recap
The Haskell Book
A function is a relation between
a set of possible inputs and a
set of possible outputs.
The function itself defines and
represents that relationship.

Haskell In Depth:
- Composition of functions
- Pure functions
- Referential transparency
- Equational reasoning
- Absence of side effects
- Abstraction

Abstraction
⭐️ Abstraction hides complex implementation details behind a simpler, higher-level interface.
⭐️ Abstraction is generalised representations of patterns, structures, properties or behaviours, and making it more widely applicable.
Publication -> Newspaper -> Aftenposten -> 11th May Aftenposten
Abstraction
⭐️ Higher level of abstraction means highly expressive and less verbose code.
⭐️ But be aware of "The Law of Leaky Abstractions"
⭐️ Higher level of abstraction can also lead to higher level of complexity.
𝜆𝑥.𝑥+1
𝜆𝑥𝑦.2𝑥+𝑦
𝜆𝑥.(𝜆𝑦.𝑥+𝑦)
CLOSED
OPEN
Combinator
CLOSED
CLOSED/closure
Free variable
In calculus, abstractions is represented by lambda expressions.
evens numbers = [x | x <- numbers, x `mod` 2 == 0]evens numbers = filter even numbersHaskell
List<Integer> filterEven(List<Integer> numbers) {
List<Integer> evenNumbers = new ArrayList<>();
for (Integer number : numbers) {
if (number % 2 == 0) {
evenNumbers.add(number);
}
}
return evenNumbers;
}numbers.stream().filter(n -> n % 2 == 0).toList()Java
public Maybe<Pair<Year, Salary>> min(Person person) {
Salary minSalary = null;
Year minYear = null;
for (Salary s : person.incomeHistory)
if (minSalary == null || isLowerSalary(s, minSalary)) {
minSalary = s;
minYear = s.year();
}
if (minSalary != null) {
return Maybe.of(new Pair<>(minYear, minSalary));
} else
return new Maybe.Nothing<>();
}
boolean isLowerSalary(Salary salary1, Salary salary2) {
return salary1.salary().compareTo(salary2.salary()) < 0;
}Function<Person,
Maybe<Pair<Year, Salary>>> min =
person -> person.incomeHistory.stream()
.min(Comparator.comparing(Salary::salary))
.map(s -> new Pair<>(s.year(), s))
.map(Maybe::ofNullable)
.orElse(new Maybe.Nothing<>());Expressions
Expressions may be values, variables, combinations of values and expressions, or functions applied to values.
As in mathematics, expressions and functions must always return a value, and the same value given the same input.
Purity
Pure functions are referentially transparent, meaning a function always returns the same result with the same inputs (hence you can replace the function with its value).
- Purity also means no side-effects (mutation of externally visible data, and no shared-mutability).
Idempotent
Combining Functions
-
Curry
-
Partial Application
-
Higher-Order Functions
-
Composition
curried functions
converting function of \(n\) arguments into \(n\) functions with a single argument each
interface IntFunction extends Function<Integer, Integer>{}
Function<Integer, IntFunction> f = x -> (y -> x + y);
println( f.apply(3).apply(4) );a function is partially applied if some of its arguments are not set
partially applied functions
interface DoubleFunction extends Function<Double, Double> {}
Function<Double, DoubleFunction> sum = x -> (y -> x * y);
Function<Double, Double> num66 = sum.apply(0.66);
Function<Double, Double> calc = num66.andThen(a -> a / 3);
var result = calc.apply(50);num :: (Fractional a) => a -> a -> a
num x y = x * y
f66 :: Double -> Double
f66 = num 0.66
calc :: Double -> Double
calc x = (/) (f66 x) 3interface DoubleFunction extends Function<Double, Double> {}
Function<Double, DoubleFunction> sum = x -> (y -> x * y);
DoubleFunction num66 = sum.apply(0.66);
DoubleFunction calc = num66.andThen(a -> a / 3);
var result = calc.apply(50);higher order functions
functions that take other functions as parameter, and/or returns a function
BiFunction<Person, Function<Person, String>, String>
map = (person, fn) -> fn.apply(person);
<T, R> Supplier<R> lazyMap(T p, Function<T, R> fn) {
return () -> fn.apply(p);
}composition
mapping output of one function to the input of another, creating a new function
$$ rs = (reverse~.~sort)$$
composition
enum VehicleType {ANY, BUS, SCHOOL};
Function<List<? super Bus>, List<? extends String>> classNames ...
Function<VehicleType, List<? super Bus>>
make = n -> switch (n) {
case BUS -> List.of(...);
case SCHOOL -> List.of(...);
case ANY -> List.of(...);
};
Function<List<String>, List<String>>
toString = s -> s.stream().map(String::toUpperCase).toList();
classNames.andThen(toString).compose(make).apply(VehicleType.BUS);1
2
3
4
Understanding the order of execution is difficult in Java.
toString( classNames( make( BUS ) ) )
Text
Morphism
Transforming shapes
Morphism is a general term that refers to the transformation or mapping between objects or structures.
Why talk about this?
-
Operations on data affect logical consistency and predictable transformations of data structures (behaviour)
- Using proper data types and operations in a way that respects the data's intended interpretation
- Some people just have a buzzword-fetish making learning harder to beginners 😔🤯
Polymorphism
A fundamental principle in object-oriented programming that allows objects of different classes to be treated as objects of a common superclass.
sealed interface Animal permits Dog, Cat {}
interface Dog extends Animal {}
interface Cat extends Animal {}
Function<Animal, String> toStringAllows flexible and reusable code by enabling objects of different classes to interact through common interfaces
Monomorphism
A function is monomorphic if it works only for one type
sealed interface Animal permits Dog, Cat {}
interface Dog extends Animal {}
interface Cat extends Animal {}
// monomorphic functions
Function<Cat, Cat> copyCat
Function<Dog, Dog> updateDog
// polymorphic
Function<Animal, String> nameHomomorphism
A function is homomorphic if it preserves structure when mapping
// Composition law: f(a + b) = f(a) + f(b)
record Price(double amount) { }
Function<Price, Price> zam =
p -> price(p.amount() * 1.10); // 10% zam
zam(price(50)) == zam(price(25)) + zam(price(25))
Price price(double amount) {
return new Price(amount);
}// Composition law: f(a + b) = f(a) + f(b)
record Price(String amount) {...}
Function<Price, Price> zam =
p -> price(""+parseDouble(p.amount()) * 1.10);
zam(price("50")) ==> "55"
zam(price("25")) + zam(price("25")) ==> "27.027.0"
Price price(String amount) {
return new Price(amount);
}Not homomorphic. Arithmetics is confused with concatenation.
Isomorphism
Isomorphism is a type of relation where two structures can be mapped to each without loss of data (aka preserving all properties)
record Point(int x, int y) {}
record Tuple(int _1, int _2) {}
Function<Point, Tuple> toTuple = p -> new Tuple(p.x, p.y)
Function<Tuple, Point> toPoint = t -> new Point(t._1, t._2)Data Modelling
How do we *think* with FP?
Data
Data holds information about the context we would like to represent in our model.
Operations
Operations process and transform the data in our model.
We represent our data and the model as values.
We represent the operations in our application as functions.
You describe your set of values (your data)
You describe operations that work on those values (your functions)
1)
2)
Source: https://docs.scala-lang.org/scala3/book/domain-modeling-fp.html
Object-orientated
Data
Operations
class Person {
private Name name;
private Address address;
private List<Salary> salaries;
Name getName() {}
Address getAddress() {}
SalaryData foldSalaries() {}
Pair<Year, Salary> max() {}
}Object-orientated
Data
Operations
Data
Operations
Functional
Object-orientated
Classes, Objects, Methods, Fields, Constructors
Modules, Types Classes, Values, Functions
Data
Operations
Data
Operations
data Name = Name String String
type Address = String
type Year = Int
type Month = Int
type Amount = Double
data Salary = Year Month Amount
data SalaryList = SalaryList [Salary]
data Person = Name Address SalaryList personName :: Person -> String
personAddress :: Person -> String
fold :: [Salary] -> SalaryData
max :: [Salary] -> (Year, Salary)
min :: [Salary] -> (Year, Salary)
Functional
-- Fold function to process a list of Salaries
fold :: [Salary] -> SalaryData
fold salaries =
let total = sum [amount | Salary _ _ amount <- salaries]
count = fromIntegral (length salaries)
average = if count > 0 then total / count else 0
in SalaryData total average
// pure
// immutableModules, Types Classes, Values, Functions
Data
record Name(String fname, String lname) {}
record Address(String address) {}
record Year(Integer year) {}
record Month(Integer month) {}
record Amount(Double amount) {}
record Salary(Year y, Month m, Amount a) {}
record SalaryList(List<Salary> salaries) {}
record Person(Name n, Address a, SalaryList s) {} data Name = Name String String
type Address = String
type Year = Int
type Month = Int
type Amount = Double
data Salary = Year Month Amount
data SalaryList = SalaryList [Salary]
data Person = Name Address SalaryListModules, Types Classes, Values, Functions
Operations
Function<Person, String> name
Function<Person, String> addr
Function<List<Salary>, SalaryData> fold
Function<List<Salary>, Pair<Year, Salary>> maxname :: Person -> String
addr :: Address -> String
fold :: [Salary] -> SalaryData
max :: [Salary] -> (Year, Salary)interface DoubleFunction extends Function<Double, Double> {}
interface F3 extends Function<Double, Function<Double, DoubleFunction>> {};
F3 fn = a -> b -> c -> a + b + c;public class PersonFP{
public record Person(Name name, Address address,
List<Salary> incomeHistory) {}
public record Name(String fullName) {}
public record Address(String street, String city, String country) {}
public record Salary(Year year, Month month, Double salary) {}
public static Function<Person, Maybe<Double>> foldSalaries =
person -> person.incomeHistory.stream()
.map(Salary::salary)
.reduce(Double::sum)
.map(Maybe::ofNullable)
.orElse(new Maybe.Nothing<>());
public static Function<Person, Maybe<Pair<Year, Salary>>> min =
person -> person.incomeHistory.stream()
.min(Comparator.comparing(Salary::salary))
.map(s -> new Pair<>(s.year(), s))
.map(Maybe::ofNullable)
.orElse(new Maybe.Nothing<>());
public static Function<Person, Maybe<Pair<Year, Salary>>> max =
person -> person.incomeHistory.stream()
.max(Comparator.comparing(Salary::salary))
.map(s -> new Pair<>(s.year(), s))
.map(Maybe::ofNullable)
.orElse(new Maybe.Nothing<>());
}Purely functional code in Java:
No side-effects or shared/visible mutability
Immutable data are possible
Functional style, expressive code
Pure functions
Algebraic Data Types (product types)
No nulls, predictable behaviour
(with equational reasoning, but there many pitfalls, such when using parallelsim )
What are the issues:
No lazy or immutability out-of-box
Composition is hard and verbose
Functional style takes time to learn
Difficult to keep overview as code base grows
Java is too verbose
Added complexity & code to maintain (ie: Either)
Weak type inference compared to Haskell
Hard to deal with side-effects in a pure style
What have I learned?
-
Declare functions
-
Compose several functions to bigger ones
-
Contain and handle side-effects in monads
-
Use pattern matching and ADTs
-
Create immutable data structures
-
Combine streams and functions
Summary - The good stuff
- Pattern matching for switch are great
- Exhaustiveness through sealed classes are great!
- Deconstructional pattern matching is great!
- Unnamed patterns and variables are finally here
- Records as (shallowly) immutable data types is good
- Streams are lazy - and supports infinite sets
- Functional style with the java.util.function interfaces can have its advantages if used wisely.
The journey
- Getting used to everything being functions
- Relearning the theory about object orientation
- Relearning concepts from mathematics
- Learning a completely different paradigm
- Many questions: state, io, loops, recursion etc
- Learning a huge library of functions
- Learning what and how to compose functions
- Learning concepts from category theory
@dervismn
Thanks!
https://bit.ly/javaday24
linkedin.com/in/dervism/
