victor.rentea@gmail.com ♦ ♦ @victorrentea ♦ VictorRentea.ro
The Hitchhiker Guider to
Victor Rentea
Best Talks, Goodies, Blog:
VictorRentea.ro
Independent Trainer & Consultant
Founder of
Bucharest Software Craftsmanship Community
Java Champion
❤️ Simple Design, Refactoring, Unit Testing ❤️
Technical Training
Hibernate
Spring Func Prog in Java
300+ days
2000 devs
8 years
Training for you or your company: VictorRentea.ro
40 companies
Follow me:
35K 4K 3K
Java Performance
Reactive-X
Design Patterns
Clean Code
Refactoring
Unit Testing
TDD
any
lang
174 © VictorRentea.ro
a training by
Life
175 © VictorRentea.ro
a training by
checkCustomer(customer);
checkOrder(customer, order);
Mock-full tests
Race Bugs
A method changes a parameter: Surprise!
Unexpected Different Results for Same Inputs
customer.setActive(true);
Temporal Coupling
176 © VictorRentea.ro
a training by
do side-effects
return void sendEmail(Email):void
Command-Query Separation
setActive(true):void
return results
search(criteria):List
computePrice(flight):int
in 1994, by Bertrand Meyer
Pure Functions
177 © VictorRentea.ro
a training by
No side effects
No INSERTs, POSTs, queues, files, fields,…
= 𝑀𝑎𝑡ℎ𝑒𝑚𝑎𝑡𝑖𝑐𝑎𝑙 𝐹𝑢𝑛𝑐𝑡𝑖𝑜𝑛𝑠: 𝑓 𝑥, 𝑦 = 𝑥2
+ 𝑦
(logging doesn't count)
Referential Transparent
Same Inputs ➔ Same Output
No current time, random, GET, SELECT…
≠ Idempotent
Pure Functions
178 © VictorRentea.ro
a training by
Referential Transparent Idempotent
≠
f(1,2) = 3
f(1,2) can be replaced with 3 everywhere
After calling f once
Calling it again n times
will not produce any extra changes
Can Have Side Effects
eg: DELETE FROM X WHERE ID =
f(1,2)
f(1,2)
f(1,2)
179 © 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
180 © 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
181 © VictorRentea.ro
a training by
Why we Love Pure Functions
➢No hidden inputs, only plain-sight return values and parameters
➢Easier to understand
➢No temporal coupling
➢Testable with less setup
➢Fast & Composable: free to call them n times ➔
➢Parallelizable
(careful with instance functions on mutable objects)
r=f();
a=g(r);
182 © VictorRentea.ro
a training by
Replace Temp Variable with Query
If a function is pure + fast, it's safe to call it multiple times:
Replace Parameter with Query
var data = f(a,b);
data
data
f(a,b)
f(a,b)
big(..., f(a,b));
param
param
void big(..., param) {
f(a,b)
f(a,b)
they typically are
183 © VictorRentea.ro
a training by
you don't care how many times
(and if) you call a pure function
184 © VictorRentea.ro
a training by
185 © VictorRentea.ro
a training by
That's it!
I'll make all my functions pure
that's usually impossible
What kind of app doesn't change anything?
186 © VictorRentea.ro
a training by
In Java there's no way to strictly enforce purity
➔ We have to live with both pure and impure functions
How do we distinguish them?
187 © 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
188 © VictorRentea.ro
a training by
You can do better!
189 © VictorRentea.ro
a training by
190 © VictorRentea.ro
a training by
functional core
Side-effects (Writes) +
Non-deterministic Reads
Expose them
Purify the most complex parts of your logic!
191 © VictorRentea.ro
a training by
Purify the most complex parts of your logic!
193 © 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
194 © VictorRentea.ro
a training by
No Files in Functional Core
196 © VictorRentea.ro
a training by
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()
Expose DB and HTTP calls
imperative shell
197 © VictorRentea.ro
a training by
Expose DB and HTTP calls
Initial Read
Intermediary
(conditional?)
➔ Pass as Parameters
➔ Split-Phase Refactor
r=read();
f(r);
r1=phase1(...)
phase2(r,r1...)
expose impurity
Writing Results ➔ Return Changes w=f();
persist(w);
r=read()
imperative shell
Create new classes 💪
198 © VictorRentea.ro
a training by
Implement most complex logic
as internal pure functions
exposing impurity to the surface
199 © VictorRentea.ro
a training by
Pure Functions don't
Change Objects' State
Immutable Objects
200 © VictorRentea.ro
a training by
Immutable Objects
201 © 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
...
...
202 © 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
Immutable Data
...
...
203 © 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
x=
204 © 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);
}
// 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
final fields,
but not strictly required
record
(java 15)
Java collections are mutable😞
final
Afraid of hackers? 😨
@lombok.Value
or, until then...
205 © VictorRentea.ro
a training by
Oh, so
we CAN mutate them!
206 © VictorRentea.ro
a training by
A function changing an immutable object has to return it:
data = updx(data);
Imagine data
has 20 fields
... every time
data = updy(data);
data = updz(data);
The mess is still here!
🎵 Who changed the field X?
How to fix?
207 © VictorRentea.ro
a training by
data = updx(data);
data = updy(data);
data = updz(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
data.xyz = createXYZ(...);
➔ break them
If they change together,
they stick together
= clutter; Extreme:
Mark only the non-final with @Var
(errorprone Java compiler from Google)
208 © VictorRentea.ro
a training by
Wait a second,
I know...
209 © 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)
210 © VictorRentea.ro
a training by
Don't
ever
mutate
collections!
➔ Create new ones
211 © 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
➔ their hashCode
doesn't change
212 © VictorRentea.ro
a training by
All right cowboy!
Only immutable objects from now on!
usually that's too much!
214 © VictorRentea.ro
a training by
instead,
Extract immutable Value Objects from their fields
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?
215 © VictorRentea.ro
a training by
@Entity
(mutable)
Persistent
Immutable Leaf
eg. FullName
Immutable Objects in Real Life
Non-Persistent
Runtime Objects
that you write heavy logic with
continuously break
Large Entities
@Embeddable
216 © VictorRentea.ro
a training by
Non-Persistent
Runtime Objects
that you write heavy logic with
Always Immutable
218 © VictorRentea.ro
a training by
The Big Deal
219 © 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
Return multiple changes:
220 © VictorRentea.ro
a training by
The Big Deal
Is when immutable objects travel lots of code
221 © VictorRentea.ro
a training by
Performance of Immutability
222 © VictorRentea.ro
a training by
Concerned of Performance?
Measure it !
(and you might have a surprise)
224 © VictorRentea.ro
a training by
Avoid Immutable Objects If
- Trashing millions of instances/second
- Cloning Lost of Collections
- Trivial logic (overkill)
- Persistent Entities or DTOs
225 © 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 ➔
226 © 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 @dev nexus 2021

  • 1.
    victor.rentea@gmail.com ♦ ♦@victorrentea ♦ VictorRentea.ro The Hitchhiker Guider to
  • 2.
    Victor Rentea Best Talks,Goodies, Blog: VictorRentea.ro Independent Trainer & Consultant Founder of Bucharest Software Craftsmanship Community Java Champion ❤️ Simple Design, Refactoring, Unit Testing ❤️
  • 3.
    Technical Training Hibernate Spring FuncProg in Java 300+ days 2000 devs 8 years Training for you or your company: VictorRentea.ro 40 companies Follow me: 35K 4K 3K Java Performance Reactive-X Design Patterns Clean Code Refactoring Unit Testing TDD any lang
  • 4.
    174 © VictorRentea.ro atraining by Life
  • 5.
    175 © VictorRentea.ro atraining by checkCustomer(customer); checkOrder(customer, order); Mock-full tests Race Bugs A method changes a parameter: Surprise! Unexpected Different Results for Same Inputs customer.setActive(true); Temporal Coupling
  • 6.
    176 © VictorRentea.ro atraining by do side-effects return void sendEmail(Email):void Command-Query Separation setActive(true):void return results search(criteria):List computePrice(flight):int in 1994, by Bertrand Meyer Pure Functions
  • 7.
    177 © VictorRentea.ro atraining by No side effects No INSERTs, POSTs, queues, files, fields,… = 𝑀𝑎𝑡ℎ𝑒𝑚𝑎𝑡𝑖𝑐𝑎𝑙 𝐹𝑢𝑛𝑐𝑡𝑖𝑜𝑛𝑠: 𝑓 𝑥, 𝑦 = 𝑥2 + 𝑦 (logging doesn't count) Referential Transparent Same Inputs ➔ Same Output No current time, random, GET, SELECT… ≠ Idempotent Pure Functions
  • 8.
    178 © VictorRentea.ro atraining by Referential Transparent Idempotent ≠ f(1,2) = 3 f(1,2) can be replaced with 3 everywhere After calling f once Calling it again n times will not produce any extra changes Can Have Side Effects eg: DELETE FROM X WHERE ID = f(1,2) f(1,2) f(1,2)
  • 9.
    179 © 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
  • 10.
    180 © 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
  • 11.
    181 © VictorRentea.ro atraining by Why we Love Pure Functions ➢No hidden inputs, only plain-sight return values and parameters ➢Easier to understand ➢No temporal coupling ➢Testable with less setup ➢Fast & Composable: free to call them n times ➔ ➢Parallelizable (careful with instance functions on mutable objects) r=f(); a=g(r);
  • 12.
    182 © VictorRentea.ro atraining by Replace Temp Variable with Query If a function is pure + fast, it's safe to call it multiple times: Replace Parameter with Query var data = f(a,b); data data f(a,b) f(a,b) big(..., f(a,b)); param param void big(..., param) { f(a,b) f(a,b) they typically are
  • 13.
    183 © VictorRentea.ro atraining by you don't care how many times (and if) you call a pure function
  • 14.
  • 15.
    185 © VictorRentea.ro atraining by That's it! I'll make all my functions pure that's usually impossible What kind of app doesn't change anything?
  • 16.
    186 © VictorRentea.ro atraining by In Java there's no way to strictly enforce purity ➔ We have to live with both pure and impure functions How do we distinguish them?
  • 17.
    187 © 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
  • 18.
    188 © VictorRentea.ro atraining by You can do better!
  • 19.
  • 20.
    190 © VictorRentea.ro atraining by functional core Side-effects (Writes) + Non-deterministic Reads Expose them Purify the most complex parts of your logic!
  • 21.
    191 © VictorRentea.ro atraining by Purify the most complex parts of your logic!
  • 22.
    193 © 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
  • 23.
    194 © VictorRentea.ro atraining by No Files in Functional Core
  • 24.
    196 © VictorRentea.ro atraining by 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() Expose DB and HTTP calls imperative shell
  • 25.
    197 © VictorRentea.ro atraining by Expose DB and HTTP calls Initial Read Intermediary (conditional?) ➔ Pass as Parameters ➔ Split-Phase Refactor r=read(); f(r); r1=phase1(...) phase2(r,r1...) expose impurity Writing Results ➔ Return Changes w=f(); persist(w); r=read() imperative shell Create new classes 💪
  • 26.
    198 © VictorRentea.ro atraining by Implement most complex logic as internal pure functions exposing impurity to the surface
  • 27.
    199 © VictorRentea.ro atraining by Pure Functions don't Change Objects' State Immutable Objects
  • 28.
    200 © VictorRentea.ro atraining by Immutable Objects
  • 29.
    201 © 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 ... ...
  • 30.
    202 © 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 Immutable Data ... ...
  • 31.
    203 © 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 x=
  • 32.
    204 © 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); } // 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 final fields, but not strictly required record (java 15) Java collections are mutable😞 final Afraid of hackers? 😨 @lombok.Value or, until then...
  • 33.
    205 © VictorRentea.ro atraining by Oh, so we CAN mutate them!
  • 34.
    206 © VictorRentea.ro atraining by A function changing an immutable object has to return it: data = updx(data); Imagine data has 20 fields ... every time data = updy(data); data = updz(data); The mess is still here! 🎵 Who changed the field X? How to fix?
  • 35.
    207 © VictorRentea.ro atraining by data = updx(data); data = updy(data); data = updz(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 data.xyz = createXYZ(...); ➔ break them If they change together, they stick together = clutter; Extreme: Mark only the non-final with @Var (errorprone Java compiler from Google)
  • 36.
    208 © VictorRentea.ro atraining by Wait a second, I know...
  • 37.
    209 © 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)
  • 38.
    210 © VictorRentea.ro atraining by Don't ever mutate collections! ➔ Create new ones
  • 39.
    211 © 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 ➔ their hashCode doesn't change
  • 40.
    212 © VictorRentea.ro atraining by All right cowboy! Only immutable objects from now on! usually that's too much!
  • 41.
    214 © VictorRentea.ro atraining by instead, Extract immutable Value Objects from their fields 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?
  • 42.
    215 © VictorRentea.ro atraining by @Entity (mutable) Persistent Immutable Leaf eg. FullName Immutable Objects in Real Life Non-Persistent Runtime Objects that you write heavy logic with continuously break Large Entities @Embeddable
  • 43.
    216 © VictorRentea.ro atraining by Non-Persistent Runtime Objects that you write heavy logic with Always Immutable
  • 44.
    218 © VictorRentea.ro atraining by The Big Deal
  • 45.
    219 © 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 Return multiple changes:
  • 46.
    220 © VictorRentea.ro atraining by The Big Deal Is when immutable objects travel lots of code
  • 47.
    221 © VictorRentea.ro atraining by Performance of Immutability
  • 48.
    222 © VictorRentea.ro atraining by Concerned of Performance? Measure it ! (and you might have a surprise)
  • 49.
    224 © VictorRentea.ro atraining by Avoid Immutable Objects If - Trashing millions of instances/second - Cloning Lost of Collections - Trivial logic (overkill) - Persistent Entities or DTOs
  • 50.
    225 © 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 ➔
  • 51.
    226 © VictorRentea.ro atraining by victorrentea@gmail.com ♦ ♦ Training: VictorRentea.ro ➢We'll change it in there ➔ compute and return