KEMBAR78
Pure Functions and Immutable Objects | PDF
victor.rentea@gmail.com ♦ ♦ @victorrentea ♦ VictorRentea.ro
The Hitchhiker Guider to
Victor Rentea
Best Talks:
VictorRentea.ro
Independent Trainer & Consultant
Founder of
Bucharest Software Craftsmanship Community
Java Champion
❤️ Simple Design, Refactoring, Unit Testing ❤️
Technical Training
HibernateSpring Func Prog in Java
300+ days2000 devs8 years
Details for you or your company: VictorRentea.ro
40 companies
Follow me:
35K 4K 3K
Java PerformanceReactive-X
Design Patterns
Clean Code
Refactoring
Unit Testing
TDD
any
lang
162 © VictorRentea.ro
a training by
Life
163 © VictorRentea.ro
a training by
checkCustomer(customer);
checkOrder(customer, order);
Mock-full tests
Race Bugs
A method changes a parameter: Surprise!
Different Results for Same Inputs
customer.setActive(true);
Temporal Coupling
164 © VictorRentea.ro
a training by
do side-effects
return void sendEmail(Email):void
Command-Query Separation
setActive(true):void
return resultssearch(criteria):List
computePrice(movie):int
in 1994, by Bertrand Meyer
Pure Functions
165 © VictorRentea.ro
a training by
No side effects
No INSERTs, POSTs, queues, files, fields,…
= 𝑀𝑎𝑡ℎ𝑒𝑚𝑎𝑡𝑖𝑐𝑎𝑙 𝐹𝑢𝑛𝑐𝑡𝑖𝑜𝑛𝑠: 𝑓 𝑥, 𝑦 = 𝑥2
+ 𝑦
(logging doesn't count)
Referential Transparent
Same arguments ➔ same result
No current time, random, GET, SELECT…
≠ Idempotent
Pure Functions
167 © VictorRentea.ro
a training by
Pure Functions : Quiz
f1(int x) {return x + 1;}
f2(Data d) {return ++d.x;}
f3() {d.incrementX(); return d.x;}
f4() {return querySQL(...);}
f5(int y) { return this.x + y; }
f6(Data d, int y) { return d.getX() + y; }
f7(int i) { if (i<0) throw new WrongInputException(); }
is this immutable?
Probable side effects
Expected to be pure
168 © VictorRentea.ro
a training by
throw new E(); is pure
f(x) {
try {
//
}
}
catch (E) is pure?
if it always throws for the same inputs
it depends ...
* Some slightly disagree
on E
NO, if E can happen randomly
eg. IOException, OutOfMemory
YES, if E is thrown deterministically*
➔ Catch unexpected exceptions
in the outskirts of your code
169 © VictorRentea.ro
a training by
Why we Love Pure Functions
➢No hidden inputs, only plain-sight return values and parameters
➢Easier to understand
➢Testable (less setup)
➢Fast & Composable: free to call them n times
➢No temporal coupling
➢Parallelizable
170 © VictorRentea.ro
a training by
That's it!
I'll only write pure functions from now!
impossible
What kind of app doesn't change anything?
172 © VictorRentea.ro
a training by
In Java there's no way to strictly enforce purity
So we'll live with both pure and impure functions
But how do we distinguish them?
173 © VictorRentea.ro
a training by
do side-effects
return void sendEmail(Email):void
Command-Query Separation
setActive(true):void
return results
pure functions
search():List
computePrice(movie):int
Highlight Side Effects
computePriceAndAdjustMetrics(movie):int
174 © VictorRentea.ro
a training by
You can do better!
178 © VictorRentea.ro
a training by
179 © VictorRentea.ro
a training by
functional core
Side-effects (Writes) +
Non-deterministic Reads
Expose them
Purify the most complex parts of your logic!
180 © VictorRentea.ro
a training by
Purify the most complex parts of your logic!
182 © VictorRentea.ro
a training by
Purifying Logic
Time and Random
Amount of time-dependent logic:
➢None (e.setCreationDate(now());) ➔ tolerate
➢Very little ➔ Inject a Clock / TimeProvider
➢Heavy (x-rare) ➔ expose a ..., time); parameter
183 © VictorRentea.ro
a training by
Purifying Logic
No Files in Functional Core
185 © VictorRentea.ro
a training by
Purifying Logic
Expose Read/Write from DB/APIs
Initial Read
Intermediary
(conditional?)
➔ Pass as Parameters
➔ Split-Phase Refactor f();
r=read();
f(r);
Writing Results ➔ Return Changes w=f();
persist(w);
r=read()
186 © VictorRentea.ro
a training by
Purifying Logic
Expose Read/Write from DB/APIs
Initial Read
Intermediary
(conditional?)
➔ Pass as Parameters
➔ Split-Phase Refactor
r=read();
f(r);
r1=f1()
f2(r,r1...)
expose impurity
Writing Results ➔ Return Changes w=f();
persist(w);
r=read()
187 © VictorRentea.ro
a training by
Implement as much logic
as pure functions
exposing impurity to the surface
188 © VictorRentea.ro
a training by
Purifying Logic
Changing Objects' State
Immutable Objects
190 © VictorRentea.ro
a training by
void f(Data data) {
...
if (data.getX() == 1) {
// will this run ?
}
}
void h() {
Data data = new Data(1);
obj.setData(data);
g(data);
}
obj
void g(Data data) {
data.setX(2);
mutateParam(data);
obj.mutateField();
f(data);
}
void setData(Data data) {
this.data = data;
}
void mutateField() {
this.data.setX(2);
}
same obj
in h() and g()
void mutateParam(Data data) {
data.setX(1);
}
x=
Long-lived mutable data
A Code Inspection Session
What code ran before,
having a reference
to my data instance?!
Mutable Data
191 © VictorRentea.ro
a training by
void f(Data data) {
...
if (data.getX() == 1) {
// will this run ?
}
}
void h() {
Data data = new Data(1);
obj.setData(data);
g(data);
}
obj
void g(Data data) {
data.setX(2);
mutateParam(data);
obj.mutateField();
f(data);
}
void setData(Data data) {
this.data = data;
}
void mutateField() {
this.data.setX(2);
}
same obj
in h() and g()
void mutateParam(Data data) {
data.setX(1);
}
x=
Long-lived mutable data
A Code Inspection Session
What code ran before,
having a reference
to my data instance?!
Mutable DataImmutable Data
192 © VictorRentea.ro
a training by
void f(Data data) {
...
if (data.getX() == 1) {
// will this run ?
}
}
void g(Data data) {
f(data);
}
void h() {
Data data = new Data(1);
g(data);
}
A Code Inspection Session
Immutable Data
Who created
the instance?!
Easier to trace
data changes
Real-world: 4+ functions in between
...
Real-world: 4+
...
x=
193 © VictorRentea.ro
a training by
Designing Immutable Classes
public class A {
private final String s;
private final B b;
private final List<String> list;
public A(String s, B b, List<String> list) {
this.s = s;
this.b = b;
this.list = new ArrayList<>(list);
// validation ...
}
public List<String> getList() {
return unmodifiableList(list);
}
// other getters
// hashCode, equals on all fields = Value Object
// bits of LOGIC 💪
public A withS(String newS) {
return new A(newS, b, list);
}
}
Mutates by creating
a new instance
Stops creator keeping a reference
Overkill, as problem is not the creator but the "man-in-the-middle"
Oh, so
we CAN
mutate them!
@lombok.With
Iterable<String> getList() {
List<? extends String> getList() {or
record
(java 15)
Java collections are mutable😞
final
Afraid of hackers? 😨
@lombok.Value
or, until then...
194 © VictorRentea.ro
a training by
195 © VictorRentea.ro
a training by
A function changing the object has to return it:
data = f(data);
Imagine data has 20 fields
When did data.x change?
... every time
data = g(data);
data = h(data);
The mess is still here!
196 © VictorRentea.ro
a training by
data = f(data);
final variables won't allow this
IntelliJ underlines
reassigned variables ❤️
By the way, there are ways to add final automatically at code cleanup
Real Problem
Too Large Immutable Objects
smallerData = f(...);
➔ break them
If they change together,
they stick together
data = g(data);
data = h(data);
withX
withY
withZ
197 © VictorRentea.ro
a training by
Wait a second,
I know...
198 © VictorRentea.ro
a training by
void f(VO[] arr) {
arr[0] = arr[0].withX(-99);
}
void f(List<String> list) {
list.removeIf(String::isBlank);
}
void f(Map<Integer, VO> map) {
map.put(1, map.get(1).withX(-99));
}
map.get(1).withX(-99)
199 © VictorRentea.ro
a training by
Don't
ever
mutate
collections!
➔ Create new ones
200 © VictorRentea.ro
a training by
Why we Immutable objects
Easier to trace data changes
Can enforce validation in constructor
Safe to put in Set or Map(as keys)
Thread-safe ➔ no race bugs, since they can't be changed
201 © VictorRentea.ro
a training by
All right cowboy!
Only immutable objects from now on!
usually that's too much!
202 © VictorRentea.ro
a training by
Value Object vs Reference Object
class Customer {
id // PK in DB
name
phone
}
class Money {
amount
currency
}
equals(): using what fields ?
idamount, currency
final
final
Make VOs immutable!
Should Entities
be immutable?
No matter how different two instances are,
if they have the same id, it's the same Customer
203 © VictorRentea.ro
a training by
instead,
Extract immutable Value Objects from them
Leaving the root Entity mutable
NO*
*there are few cases when it pays to, but those apps typically don't persist their data
Should Entities be immutable?
204 © VictorRentea.ro
a training by
Entity
(mutable)
Persistent Leaf
eg. FullName
Immutable Objects in Real Life
Runtime Objects
(non-persistent data)
continuously
break XL entities
Hibernate: @Embeddable
206 © VictorRentea.ro
a training by
The Big Deal
207 © VictorRentea.ro
a training by
The Big Deal
Don't mutate objects on long workflows!
a(e) b(x) c(x) d(x) e(x) f(x) g(e) {
e.setField(...);
}
a(e) {
String s = b(vo);
e.setField(s);
}
b(…) c(…) d(…) e(…) f(…) g(…) {return ...;}
1) vavr.Tuple3<String,String,Integer>
2) NewConceptVO #kudos if you can find a good name!
can be pure functions
Immutable Arguments
Return the change to the surface, and apply it there
How to return changes
to multiple fields:
208 © VictorRentea.ro
a training by
The Big Deal
Is when immutable objects travel lots of code
209 © VictorRentea.ro
a training by
Performance of Immutability
210 © VictorRentea.ro
a training by
Performance of Immutability
Measure it !
(and you might have a surprise)
212 © VictorRentea.ro
a training by
Avoid Immutable Objects If
- Trashing millions of instances/second
- Cloning Lots of Lists
- Trivial logic
- Persistent Entities
213 © VictorRentea.ro
a training by
Take-Aways
➢ Complex logic ➔ pure functions using immutable objects
➢ Functional Core / Imperative Shell
➢ Pull impure remote/DB calls in the shell
➢ We'll change it in there ➔ compute and return
➢ Without proper mindset, immutability can hurt
➢ Don't mutate: argument state, variables or collections
➢ Immutable: runtime data or persistent leaves
➢ We'll change it in there ➔ compute and return
And no, I'm against OOP; but not in huge logic code ➔
214 © VictorRentea.ro
a training by
victorrentea@gmail.com ♦ ♦ Training: VictorRentea.ro
➢We'll change it in there ➔ compute and return

Pure Functions and Immutable Objects

  • 1.
    victor.rentea@gmail.com ♦ ♦@victorrentea ♦ VictorRentea.ro The Hitchhiker Guider to
  • 2.
    Victor Rentea Best Talks: VictorRentea.ro IndependentTrainer & Consultant Founder of Bucharest Software Craftsmanship Community Java Champion ❤️ Simple Design, Refactoring, Unit Testing ❤️
  • 3.
    Technical Training HibernateSpring FuncProg in Java 300+ days2000 devs8 years Details for you or your company: VictorRentea.ro 40 companies Follow me: 35K 4K 3K Java PerformanceReactive-X Design Patterns Clean Code Refactoring Unit Testing TDD any lang
  • 4.
    162 © VictorRentea.ro atraining by Life
  • 5.
    163 © VictorRentea.ro atraining by checkCustomer(customer); checkOrder(customer, order); Mock-full tests Race Bugs A method changes a parameter: Surprise! Different Results for Same Inputs customer.setActive(true); Temporal Coupling
  • 6.
    164 © VictorRentea.ro atraining by do side-effects return void sendEmail(Email):void Command-Query Separation setActive(true):void return resultssearch(criteria):List computePrice(movie):int in 1994, by Bertrand Meyer Pure Functions
  • 7.
    165 © VictorRentea.ro atraining by No side effects No INSERTs, POSTs, queues, files, fields,… = 𝑀𝑎𝑡ℎ𝑒𝑚𝑎𝑡𝑖𝑐𝑎𝑙 𝐹𝑢𝑛𝑐𝑡𝑖𝑜𝑛𝑠: 𝑓 𝑥, 𝑦 = 𝑥2 + 𝑦 (logging doesn't count) Referential Transparent Same arguments ➔ same result No current time, random, GET, SELECT… ≠ Idempotent Pure Functions
  • 8.
    167 © VictorRentea.ro atraining by Pure Functions : Quiz f1(int x) {return x + 1;} f2(Data d) {return ++d.x;} f3() {d.incrementX(); return d.x;} f4() {return querySQL(...);} f5(int y) { return this.x + y; } f6(Data d, int y) { return d.getX() + y; } f7(int i) { if (i<0) throw new WrongInputException(); } is this immutable? Probable side effects Expected to be pure
  • 9.
    168 © VictorRentea.ro atraining by throw new E(); is pure f(x) { try { // } } catch (E) is pure? if it always throws for the same inputs it depends ... * Some slightly disagree on E NO, if E can happen randomly eg. IOException, OutOfMemory YES, if E is thrown deterministically* ➔ Catch unexpected exceptions in the outskirts of your code
  • 10.
    169 © VictorRentea.ro atraining by Why we Love Pure Functions ➢No hidden inputs, only plain-sight return values and parameters ➢Easier to understand ➢Testable (less setup) ➢Fast & Composable: free to call them n times ➢No temporal coupling ➢Parallelizable
  • 11.
    170 © VictorRentea.ro atraining by That's it! I'll only write pure functions from now! impossible What kind of app doesn't change anything?
  • 12.
    172 © VictorRentea.ro atraining by In Java there's no way to strictly enforce purity So we'll live with both pure and impure functions But how do we distinguish them?
  • 13.
    173 © VictorRentea.ro atraining by do side-effects return void sendEmail(Email):void Command-Query Separation setActive(true):void return results pure functions search():List computePrice(movie):int Highlight Side Effects computePriceAndAdjustMetrics(movie):int
  • 14.
    174 © VictorRentea.ro atraining by You can do better!
  • 15.
  • 16.
    179 © VictorRentea.ro atraining by functional core Side-effects (Writes) + Non-deterministic Reads Expose them Purify the most complex parts of your logic!
  • 17.
    180 © VictorRentea.ro atraining by Purify the most complex parts of your logic!
  • 18.
    182 © VictorRentea.ro atraining by Purifying Logic Time and Random Amount of time-dependent logic: ➢None (e.setCreationDate(now());) ➔ tolerate ➢Very little ➔ Inject a Clock / TimeProvider ➢Heavy (x-rare) ➔ expose a ..., time); parameter
  • 19.
    183 © VictorRentea.ro atraining by Purifying Logic No Files in Functional Core
  • 20.
    185 © VictorRentea.ro atraining by Purifying Logic Expose Read/Write from DB/APIs Initial Read Intermediary (conditional?) ➔ Pass as Parameters ➔ Split-Phase Refactor f(); r=read(); f(r); Writing Results ➔ Return Changes w=f(); persist(w); r=read()
  • 21.
    186 © VictorRentea.ro atraining by Purifying Logic Expose Read/Write from DB/APIs Initial Read Intermediary (conditional?) ➔ Pass as Parameters ➔ Split-Phase Refactor r=read(); f(r); r1=f1() f2(r,r1...) expose impurity Writing Results ➔ Return Changes w=f(); persist(w); r=read()
  • 22.
    187 © VictorRentea.ro atraining by Implement as much logic as pure functions exposing impurity to the surface
  • 23.
    188 © VictorRentea.ro atraining by Purifying Logic Changing Objects' State Immutable Objects
  • 24.
    190 © VictorRentea.ro atraining by void f(Data data) { ... if (data.getX() == 1) { // will this run ? } } void h() { Data data = new Data(1); obj.setData(data); g(data); } obj void g(Data data) { data.setX(2); mutateParam(data); obj.mutateField(); f(data); } void setData(Data data) { this.data = data; } void mutateField() { this.data.setX(2); } same obj in h() and g() void mutateParam(Data data) { data.setX(1); } x= Long-lived mutable data A Code Inspection Session What code ran before, having a reference to my data instance?! Mutable Data
  • 25.
    191 © VictorRentea.ro atraining by void f(Data data) { ... if (data.getX() == 1) { // will this run ? } } void h() { Data data = new Data(1); obj.setData(data); g(data); } obj void g(Data data) { data.setX(2); mutateParam(data); obj.mutateField(); f(data); } void setData(Data data) { this.data = data; } void mutateField() { this.data.setX(2); } same obj in h() and g() void mutateParam(Data data) { data.setX(1); } x= Long-lived mutable data A Code Inspection Session What code ran before, having a reference to my data instance?! Mutable DataImmutable Data
  • 26.
    192 © VictorRentea.ro atraining by void f(Data data) { ... if (data.getX() == 1) { // will this run ? } } void g(Data data) { f(data); } void h() { Data data = new Data(1); g(data); } A Code Inspection Session Immutable Data Who created the instance?! Easier to trace data changes Real-world: 4+ functions in between ... Real-world: 4+ ... x=
  • 27.
    193 © VictorRentea.ro atraining by Designing Immutable Classes public class A { private final String s; private final B b; private final List<String> list; public A(String s, B b, List<String> list) { this.s = s; this.b = b; this.list = new ArrayList<>(list); // validation ... } public List<String> getList() { return unmodifiableList(list); } // other getters // hashCode, equals on all fields = Value Object // bits of LOGIC 💪 public A withS(String newS) { return new A(newS, b, list); } } Mutates by creating a new instance Stops creator keeping a reference Overkill, as problem is not the creator but the "man-in-the-middle" Oh, so we CAN mutate them! @lombok.With Iterable<String> getList() { List<? extends String> getList() {or record (java 15) Java collections are mutable😞 final Afraid of hackers? 😨 @lombok.Value or, until then...
  • 28.
  • 29.
    195 © VictorRentea.ro atraining by A function changing the object has to return it: data = f(data); Imagine data has 20 fields When did data.x change? ... every time data = g(data); data = h(data); The mess is still here!
  • 30.
    196 © VictorRentea.ro atraining by data = f(data); final variables won't allow this IntelliJ underlines reassigned variables ❤️ By the way, there are ways to add final automatically at code cleanup Real Problem Too Large Immutable Objects smallerData = f(...); ➔ break them If they change together, they stick together data = g(data); data = h(data); withX withY withZ
  • 31.
    197 © VictorRentea.ro atraining by Wait a second, I know...
  • 32.
    198 © VictorRentea.ro atraining by void f(VO[] arr) { arr[0] = arr[0].withX(-99); } void f(List<String> list) { list.removeIf(String::isBlank); } void f(Map<Integer, VO> map) { map.put(1, map.get(1).withX(-99)); } map.get(1).withX(-99)
  • 33.
    199 © VictorRentea.ro atraining by Don't ever mutate collections! ➔ Create new ones
  • 34.
    200 © VictorRentea.ro atraining by Why we Immutable objects Easier to trace data changes Can enforce validation in constructor Safe to put in Set or Map(as keys) Thread-safe ➔ no race bugs, since they can't be changed
  • 35.
    201 © VictorRentea.ro atraining by All right cowboy! Only immutable objects from now on! usually that's too much!
  • 36.
    202 © VictorRentea.ro atraining by Value Object vs Reference Object class Customer { id // PK in DB name phone } class Money { amount currency } equals(): using what fields ? idamount, currency final final Make VOs immutable! Should Entities be immutable? No matter how different two instances are, if they have the same id, it's the same Customer
  • 37.
    203 © VictorRentea.ro atraining by instead, Extract immutable Value Objects from them Leaving the root Entity mutable NO* *there are few cases when it pays to, but those apps typically don't persist their data Should Entities be immutable?
  • 38.
    204 © VictorRentea.ro atraining by Entity (mutable) Persistent Leaf eg. FullName Immutable Objects in Real Life Runtime Objects (non-persistent data) continuously break XL entities Hibernate: @Embeddable
  • 39.
    206 © VictorRentea.ro atraining by The Big Deal
  • 40.
    207 © VictorRentea.ro atraining by The Big Deal Don't mutate objects on long workflows! a(e) b(x) c(x) d(x) e(x) f(x) g(e) { e.setField(...); } a(e) { String s = b(vo); e.setField(s); } b(…) c(…) d(…) e(…) f(…) g(…) {return ...;} 1) vavr.Tuple3<String,String,Integer> 2) NewConceptVO #kudos if you can find a good name! can be pure functions Immutable Arguments Return the change to the surface, and apply it there How to return changes to multiple fields:
  • 41.
    208 © VictorRentea.ro atraining by The Big Deal Is when immutable objects travel lots of code
  • 42.
    209 © VictorRentea.ro atraining by Performance of Immutability
  • 43.
    210 © VictorRentea.ro atraining by Performance of Immutability Measure it ! (and you might have a surprise)
  • 44.
    212 © VictorRentea.ro atraining by Avoid Immutable Objects If - Trashing millions of instances/second - Cloning Lots of Lists - Trivial logic - Persistent Entities
  • 45.
    213 © VictorRentea.ro atraining by Take-Aways ➢ Complex logic ➔ pure functions using immutable objects ➢ Functional Core / Imperative Shell ➢ Pull impure remote/DB calls in the shell ➢ We'll change it in there ➔ compute and return ➢ Without proper mindset, immutability can hurt ➢ Don't mutate: argument state, variables or collections ➢ Immutable: runtime data or persistent leaves ➢ We'll change it in there ➔ compute and return And no, I'm against OOP; but not in huge logic code ➔
  • 46.
    214 © VictorRentea.ro atraining by victorrentea@gmail.com ♦ ♦ Training: VictorRentea.ro ➢We'll change it in there ➔ compute and return