SlideShare a Scribd company logo
1 of 66
Download to read offline
λazy
by Mario Fusco
Red Hat – Principal Software Engineer
@mariofusco
Lazy Evaluation
Lazy evaluation (or call-by-name) is an evaluation
strategy which delays the evaluation of an expression
until its value is needed
I know what to do.
Wake me up when
you really need it
Strictness vs. Laziness
Strictness is a property of functions (or methods in Java).
A strict function always evaluates its arguments as soon
as they’re passed to it.
Conversely a lazy function may choose not to evaluate one
or more of its arguments and in general it will evaluate
them only when they’re actually needed.
To recap,
strictness is about doing things,
laziness is about noting things to do.
Java is a strict language ...
… with some notable (and unavoidable) exceptions
✔
Boolean operators || and &&
✔
Ternary operator ? :
✔
if ... else
✔
for/while loops
✔
Java 8 streams
Q: Can exist a totally strict language?
Java is a strict language ...
… with some notable (and unavoidable) exceptions
✔
Boolean operators || and &&
✔
Ternary operator ? :
✔
if ... else
✔
for/while loops
✔
Java 8 streams
Q: Can exist a totally strict language?
A: It’s hard if not impossible to imagine how it could work
boolean isAdult = person != null && person.getAge() >= 18;
Turning Java into a lazy language
<T> T ternary(boolean pred, T first, T second) {
if (pred) {
return first;
} else {
return second;
}
}
String val1() {
return "first";
}
String val2() {
return "second";
}
String result1 = bool ? val1() : val2();
String result2 = ternary(bool, val1(), val2());
Turning Java into a lazy language
<T> T ternary(boolean pred, Supplier<T> first, Supplier<T> second) {
if (pred) {
return first.get();
} else {
return second.get();
}
}
String val1() {
return "first";
}
String val2() {
return "second";
}
String result1 = bool ? val1() : val2();
String result2 = ternary(bool, () -> val1(), () -> val2());
A simple practical example: logging
// pre-Java 8 style optimization
if (logger.isTraceEnabled()) {
logger.trace("Some long-running operation returned {}",
expensiveOperation());
}
A simple practical example: logging
// pre-Java 8 style optimization
if (logger.isTraceEnabled()) {
logger.trace("Some long-running operation returned {}",
expensiveOperation());
}
// Java 8 style optimization using laziness
logger.trace("Some long-running operation returned {}",
() -> expensiveOperation());
* from Apache Log4J 2 docs
no need to explicitly check the log level:
the lambda expression is not evaluated
if the TRACE level is not enabled *
Laziness: the ultimate performance
optimization technique
Performance optimization pro tip:
before trying to inline/optimize/parallelize a piece of code,
ask yourself if you could avoid to run it at all.
Laziness is
probably the
only form of
performance
optimization
which is (almost)
never premature
There is nothing so useless as doing efficiently
something that should not be done at all
The case of Java 8 Streams
IntStream.iterate( 1, i -> i+1 )
.map( i -> i * 2 )
.filter( i -> i > 5 )
.findFirst();
The case of Java 8 Streams
IntStream.iterate( 1, i -> i+1 )
.map( i -> i * 2 )
.filter( i -> i > 5 )
.findFirst();
Thank to laziness the Stream
can be potentially infinite
The case of Java 8 Streams
IntStream.iterate( 1, i -> i+1 )
.map( i -> i * 2 )
.filter( i -> i > 5 )
.findFirst();
Thank to laziness the Stream
can be potentially infinite
Intermediate operations are lazy:
they don’t perform any action until
a terminal operation is reached
The case of Java 8 Streams
IntStream.iterate( 1, i -> i+1 )
.map( i -> i * 2 )
.filter( i -> i > 5 )
.findFirst();
Thank to laziness the Stream
can be potentially infinite
Intermediate operations are lazy:
they don’t perform any action until
a terminal operation is reached
Only the terminal operation
triggers the pipeline of
computations
A Stream is not a data structure.
It is the lazy specification of a how
to manipulate a set of data.
Things you can’t do without laziness
There are several algorithms that can’t be
(reasonably) implemented without laziness.
For example let’s consider the following:
1. Take the list of positive integers.
2. Filter the primes.
3. Return the list of the first ten results.
Wait! I can achieve the same with a
strict algorithm
Yes, but how?
1. Take the first integer.
2. Check whether it’s a prime.
3. If it is, store it in a list.
4. Check whether the list has ten elements.
5. If it has ten elements, return it as the result.
6. If not, increment the integer by 1.
7. Go to line 2.
Wait! I can achieve the same with a
strict algorithm
Yes, but how?
1. Take the first integer.
2. Check whether it’s a prime.
3. If it is, store it in a list.
4. Check whether the list has ten elements.
5. If it has ten elements, return it as the result.
6. If not, increment the integer by 1.
7. Go to line 2.
Sure, doable …
but what a mess!
Laziness lets us separate the description of an
expression from the evaluation of that expression
Laziness is an enabler for separation of concerns
List<String> errors =
Files.lines(Paths.get(fileName))
.filter(l -> l.startsWith("ERROR"))
.limit(40)
.collect(toList());
Separation of Concerns
List<String> errors = new ArrayList<>();
int errorCount = 0;
File file = new File(fileName);
String line = file.readLine();
while (errorCount < 40 && line != null) {
if (line.startsWith("ERROR")) {
errors.add(line);
errorCount++;
}
line = file.readLine();
}
Cool! Now I know:
I will use a Stream
also for prime
numbers
Cool! Now I know:
I will use a Stream
also for prime
numbers
Let’s give this a try ...
Creating a Stream of prime numbers
public IntStream primes(int n) {
return IntStream.iterate(2, i -> i + 1)
.filter(this::isPrime)
.limit(n);
}
public boolean isPrime(int candidate) {
return IntStream.rangeClosed(2, (int)Math.sqrt(candidate))
.noneMatch(i -> candidate % i == 0);
}
Creating a Stream of prime numbers
public IntStream primes(int n) {
return IntStream.iterate(2, i -> i + 1)
.filter(this::isPrime)
.limit(n);
}
public boolean isPrime(int candidate) {
return IntStream.rangeClosed(2, (int)Math.sqrt(candidate))
.noneMatch(i -> candidate % i == 0);
}
It iterates through every number every time to see if it can be
exactly divided by a candidate number, but it would be enough
to only test numbers that have been already classified as prime
Inefficient
Recursively creating a Stream of primes
static Intstream numbers() {
return IntStream.iterate(2, n -> n + 1);
}
static int head(IntStream numbers) {
return numbers.findFirst().getAsInt();
}
static IntStream tail(IntStream numbers) {
return numbers.skip(1);
}
static IntStream primes(IntStream numbers) {
int head = head(numbers());
return IntStream.concat(
IntStream.of(head),
primes(tail(numbers).filter(n -> n % head != 0))
);
}
Cannot invoke 2
terminal operations
on the same Streams
Problems?
No lazy evaluation
in Java leads to an
endless recursion
Lazy evaluation in Scala
def numbers(n: Int): Stream[Int] = n #:: numbers(n+1)
def primes(numbers: Stream[Int]): Stream[Int] =
numbers.head #::
primes(numbers.tail filter (n -> n % numbers.head != 0))
lazy concatenation
In Scala the #:: method (lazy concatenation)
returns immediately and the elements are
evaluated only when needed
interface HeadTailList<T> {
T head();
HeadTailList<T> tail();
boolean isEmpty();
HeadTailList<T> filter(Predicate<T> p);
}
Implementing a lazy list in Java
class LazyList<T> implements HeadTailList<T> {
private final T head;
private final Supplier<HeadTailList<T>> tail;
public LazyList(T head,
Supplier<HeadTailList<T>> tail) {
this.head = head;
this.tail = tail;
}
public T head() { return head; }
public HeadTailList<T> tail() { return tail.get(); }
public boolean isEmpty() { return false; }
}
… and its lazy filter
class LazyList<T> implements HeadTailList<T> { ...
public HeadTailList<T> filter(Predicate<T> p) {
return isEmpty() ?
this :
p.test(head()) ?
new LazyList<>(head(), () -> tail().filter(p)) :
tail().filter(p);
}
} 2
3 4
5
6
7
8
9
2
3
5
7
Back to generating primes
static HeadTailList<Integer> primes(HeadTailList<Integer> numbers) {
return new LazyList<>(
numbers.head(),
() -> primes(numbers.tail()
.filter(n -> n % numbers.head() != 0)));
}
static LazyList<Integer> from(int n) {
return new LazyList<Integer>(n, () -> from(n+1));
}
Back to generating primes
static HeadTailList<Integer> primes(HeadTailList<Integer> numbers) {
return new LazyList<>(
numbers.head(),
() -> primes(numbers.tail()
.filter(n -> n % numbers.head() != 0)));
}
static LazyList<Integer> from(int n) {
return new LazyList<Integer>(n, () -> from(n+1));
}
LazyList<Integer> numbers = from(2);
int two = primes(numbers).head();
int three = primes(numbers).tail().head();
int five = primes(numbers).tail().tail().head();
LazyList of primes under the hood
from(2) → 2 () -> from(3)
2 () -> primes( from(3).filter(2) )primes(from(2)) →
LazyList of primes under the hood
from(2) → 2 () -> from(3)
2 () -> primes( from(3).filter(2) )
3 () -> from(4).filter(2).filter(3)() -> primes( )
3 () -> primes( from(4).filter(2).filter(3) )
primes(from(2)) →
.tail() →
LazyList of primes under the hood
from(2) → 2 () -> from(3)
2 () -> primes( from(3).filter(2) )
3 () -> from(4).filter(2).filter(3)() -> primes( )
3 () -> primes( from(4).filter(2).filter(3) )
5 () -> from(6).filter(2).filter(3).filter(5)() -> primes( )
5 () -> primes( from(6).filter(2).filter(3).filter(5) )
primes(from(2)) →
.tail() →
.tail() →
Printing primes
static <T> void printAll(HeadTailList<T> list) {
while (!list.isEmpty()){
System.out.println(list.head());
list = list.tail();
}
}
printAll(primes(from(2)));
iteratively
Printing primes
static <T> void printAll(HeadTailList<T> list) {
while (!list.isEmpty()){
System.out.println(list.head());
list = list.tail();
}
}
printAll(primes(from(2)));
static <T> void printAll(HeadTailList<T> list) {
if (list.isEmpty()) return;
System.out.println(list.head());
printAll(list.tail());
}
printAll(primes(from(2)));
iteratively
recursively
Iteration vs. Recursion
External Iteration
public int sumAll(int n) {
int result = 0;
for (int i = 0; i <= n; i++) {
result += i;
}
return result;
}
Internal Iteration
public static int sumAll(int n) {
return IntStream.rangeClosed(0, n).sum();
}
Iteration vs. Recursion
External Iteration
public int sumAll(int n) {
int result = 0;
for (int i = 0; i <= n; i++) {
result += i;
}
return result;
}
Recursion
public int sumAll(int n) {
return n == 0 ? 0 : n + sumAll(n - 1);
}
Internal Iteration
public static int sumAll(int n) {
return IntStream.rangeClosed(0, n).sum();
}
public class PalindromePredicate implements Predicate<String> {
@Override public boolean test(String s) {
return isPalindrome(s, 0, s.length()-1);
}
private boolean isPalindrome(String s, int start, int end) {
while (start < end && !isLetter(s.charAt(start))) start++;
while (start < end && !isLetter(s.charAt(end))) end--;
if (start >= end) return true;
if (toLowerCase(s.charAt(start)) !=
toLowerCase(s.charAt(end))) return false;
return isPalindrome(s, start+1, end-1);
}
}
Another Recursive Example
Tail Rescursive Call
What's the problem?
List<String> sentences = asList( "Dammit, I’m mad!",
"Rise to vote, sir!",
"Never odd or even",
"Never odd and even",
"Was it a car or a cat I saw?",
"Was it a car or a dog I saw?",
VERY_LONG_PALINDROME );
sentences.stream()
.filter(new PalindromePredicate())
.forEach(System.out::println);
What's the problem?
List<String> sentences = asList( "Dammit, I’m mad!",
"Rise to vote, sir!",
"Never odd or even",
"Never odd and even",
"Was it a car or a cat I saw?",
"Was it a car or a dog I saw?",
VERY_LONG_PALINDROME );
sentences.stream()
.filter(new PalindromePredicate())
.forEach(System.out::println);
Exception in thread "main" java.lang.StackOverflowError
at java.lang.Character.getType(Character.java:6924)
at java.lang.Character.isLetter(Character.java:5798)
at java.lang.Character.isLetter(Character.java:5761)
at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:17)
at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21)
at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21)
at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21)
……..
Tail Call Optimization
int func_a(int data) {
data = do_this(data);
return do_that(data);
} ... | executing inside func_a()
push EIP | push current instruction pointer on stack
push data | push variable 'data' on the stack
jmp do_this | call do_this() by jumping to its address
... | executing inside do_this()
push EIP | push current instruction pointer on stack
push data | push variable 'data' on the stack
jmp do_that | call do_that() by jumping to its address
... | executing inside do_that()
pop data | prepare to return value of 'data'
pop EIP | return to do_this()
pop data | prepare to return value of 'data'
pop EIP | return to func_a()
pop data | prepare to return value of 'data'
pop EIP | return to func_a() caller
...
caller
Tail Call Optimization
int func_a(int data) {
data = do_this(data);
return do_that(data);
} ... | executing inside func_a()
push EIP | push current instruction pointer on stack
push data | push variable 'data' on the stack
jmp do_this | call do_this() by jumping to its address
... | executing inside do_this()
push EIP | push current instruction pointer on stack
push data | push variable 'data' on the stack
jmp do_that | call do_that() by jumping to its address
... | executing inside do_that()
pop data | prepare to return value of 'data'
pop EIP | return to do_this()
pop data | prepare to return value of 'data'
pop EIP | return to func_a()
pop data | prepare to return value of 'data'
pop EIP | return to func_a() caller
...
caller
avoid putting
instruction
on stack
from Recursion to Tail Recursion
Recursion
public int sumAll(int n) {
return n == 0 ?
0 :
n + sumAll(n - 1);
}
from Recursion to Tail Recursion
Recursion
public int sumAll(int n) {
return n == 0 ?
0 :
n + sumAll(n - 1);
}
Tail Recursion
public int sumAll(int n) {
return sumAll(n, 0);
}
private int sumAll(int n, int acc) {
return n == 0 ?
acc :
sumAll(n – 1, acc + n);
}
Tail Recursion in Scala
def isPalindrome(s: String): Boolean = isPalindrome(s, 0, s.length-1)
@tailrec
def isPalindrome(s: String, start: Int, end: Int): Boolean = {
val pos1 = nextLetter(s, start, end)
val pos2 = prevLetter(s, start, end)
if (pos1 >= pos2) return true
if (toLowerCase(s.charAt(pos1)) !=
toLowerCase(s.charAt(pos2))) return false
isPalindrome(s, pos1+1, pos2-1)
}
@tailrec def nextLetter(s: String, start: Int, end: Int): Int =
if (start > end || isLetter(s.charAt(start))) start
else nextLetter(s, start+1, end)
@tailrec def prevLetter(s: String, start: Int, end: Int): Int =
if (start > end || isLetter(s.charAt(end))) end
else prevLetter(s, start, end-1)
Tail Recursion in Java?
Scala (and many other functional languages) automatically
perform tail call optimization at compile time
@tailrec annotation ensures the compiler will optimize a tail
recursive function (i.e. you will get a compilation failure if you
use it on a function that is not really tail recursive)
Java compiler doesn't perform any tail call optimization (and
very likely won't do it in a near future)
How can we overcome this limitation and
have StackOverflowError-free functions also
in Java tail recursive methods?
Trampolines to the rescue
A trampoline is an iteration applying a list of functions.
Each function returns the next function for the loop to run.
Func1
return
apply
Func2
return
apply
Func3
return
apply
FuncN
apply
…
result
return
Implementing the TailCall …
@FunctionalInterface
public interface TailCall<T> extends Supplier<TailCall<T>> {
default boolean isComplete() { return false; }
default T result() { throw new UnsupportedOperationException(); }
default T invoke() {
return Stream.iterate(this, TailCall::get)
.filter(TailCall::isComplete)
.findFirst()
.get()
.result();
}
static <T> TailCall<T> done(T result) {
return new TerminalCall<T>(result);
}
}
… and the terminal TailCall
public class TerminalCall<T> implements TailCall<T> {
private final T result;
public TerminalCall( T result ) {
this.result = result;
}
@Override
public boolean isComplete() { return true; }
@Override
public T result() { return result; }
@Override
public TailCall<T> get() {
throw new UnsupportedOperationException();
}
}
Using the Trampoline
public class PalindromePredicate implements Predicate<String> {
@Override public boolean test(String s) {
return isPalindrome(s, 0, s.length()-1).invoke();
}
private TailCall<Boolean> isPalindrome(String s, int start,
int end) {
while (start < end && !isLetter(s.charAt(start))) start++;
while (end > start && !isLetter(s.charAt(end))) end--;
if (start >= end) return done(true);
if (toLowerCase(s.charAt(start)) !=
toLowerCase(s.charAt(end))) return done(false);
int newStart = start + 1;
int newEnd = end - 1;
return () -> isPalindrome(s, newStart, newEnd);
}
}
What else laziness can do for us?
Avoiding eager dependency injection by
lazily providing arguments to
computation only when they are needed
i.e.
Introducing the Reader Monad
What’s wrong with annotation-
based dependency injection?
➢ Eager in nature
➢ “new” keyword is forbidden
➢ All-your-beans-belong-to-us
syndrome
➢ Complicated objects lifecycle
➢ Depending on scope may
not work well with threads
➢ Hard to debug if something
goes wrong
➢ Easy to abuse leading to
broken encapsulation
Annotation based
dependency injection
transforms what
should be a compile
time problem into a
runtime one
(often hard to debug)
Introducing the Reader monad ...
public class Reader<R, A> {
private final Function<R, A> exec;
public Reader( Function<R, A> exec ) {
this.exec = exec;
}
public <B> Reader<R, B> map(Function<A, B> f) {
return new Reader<>( exec.andThen(f) );
}
public <B> Reader<R, B> flatMap(Function<A, Reader<R, B>> f) {
return new Reader<>( r -> exec.andThen(f).apply(r).apply(r) );
}
public A apply(R r) {
return exec.apply( r );
}
}
The reader monad provides an environment to
wrap an abstract computation without evaluating it
The Reader Monad
The Reader monad makes
a lazy computation
explicit
in the type system, while
hiding
the logic to apply it
In other words the reader monad
allows us to treat functions as
values with a context
We can act as if we already know
what the functions will return.
@FunctionalInterface
public interface Logger extends Consumer<String> { }
public class Account {
private Logger logger;
private String owner;
private double balance;
public Account open( String owner ) {
this.owner = owner;
logger.accept( "Account opened by " + owner );
return this;
}
public Account credit( double value ) {
balance += value;
logger.accept( "Credited " + value + " to " + owner );
return this;
}
public Account debit( double value ) {
balance -= value;
logger.accept( "Debited " + value + " to " + owner );
return this;
}
public double getBalance() { return balance; }
public void setLogger( Logger logger ) { this.logger = logger; }
}
Usually injected
Account account = new Account();
account.open( "Alice" )
.credit( 200.0 )
.credit( 300.0 )
.debit( 400.0 );
The joys of dependency injection
Account account = new Account();
account.open( "Alice" )
.credit( 200.0 )
.credit( 300.0 )
.debit( 400.0 );
Throws NPE if for some
reason the logger
couldn’t be injected
The joys of dependency injection :(
You should never use “new”
public static <R, A> Reader<R, A> lift( A obj,
BiConsumer<A, R> injector ) {
return new Reader<>( r -> {
injector.accept( obj, r );
return obj;
} );
}
Lazy injection with the reader monad
public static <R, A> Reader<R, A> lift( A obj,
BiConsumer<A, R> injector ) {
return new Reader<>( r -> {
injector.accept( obj, r );
return obj;
} );
}
Lazy injection with the reader monad
Account account = new Account();
Reader<Logger, Account> reader =
lift(account, Account::setLogger )
.map( a -> a.open( "Alice" ) )
.map( a -> a.credit( 200.0 ) )
.map( a -> a.credit( 300.0 ) )
.map( a -> a.debit( 400.0 ) );
reader.apply( System.out::println );
System.out.println(account + " has balance " + account.getBalance());
public static <R, A> Reader<R, A> lift( A obj,
BiConsumer<A, R> injector ) {
return new Reader<>( r -> {
injector.accept( obj, r );
return obj;
} );
}
Lazy injection with the reader monad
Account account = new Account();
Reader<Logger, Account> reader =
lift(account, Account::setLogger )
.map( a -> a.open( "Alice" ) )
.map( a -> a.credit( 200.0 ) )
.map( a -> a.credit( 300.0 ) )
.map( a -> a.debit( 400.0 ) );
reader.apply( System.out::println );
System.out.println(account + " has balance " + account.getBalance());
Replacing injection with
function application
Account
Function<Logger, Account>
Reader
Function<Logger, Account>
Reader
Function<Logger, Account>
Reader
Function<Logger, Account>
Reader
Function<Logger, Account>
Reader
lift
apply(logger)
account
map(Function<Account, Account>)
map(Function<Account, Account>)
Function based injection
Account account = new Account();
Function<Logger, Account> inject = l -> {
account.setLogger( l );
return account;
};
Function<Logger, Account> f = inject
.andThen( a -> a.open( "Alice" ) )
.andThen( a -> a.credit( 200.0 ) )
.andThen( a -> a.credit( 300.0 ) )
.andThen( a -> a.debit( 400.0 ) );
f.apply( System.out::println );
System.out.println(account + " has balance " + account.getBalance());
The reader monad provides a more
structured and powerful approach.
In this simple case a simple function composition
is enough to achieve the same result.
public class Account { ...
public MoneyTransfer tranfer( double value ) {
return new MoneyTransfer( this, value );
}
}
public class MoneyTransfer {
private Logger logger;
private final Account account;
private final double amount;
public MoneyTransfer( Account account, double amount ) {
this.account = account;
this.amount = amount;
}
public void setLogger( Logger logger ) { this.logger = logger; }
public MoneyTransfer execute() {
account.debit( amount );
logger.accept( "Transferred " + amount + " from " + account );
return this;
}
}
Injecting into multiple objects
Injecting into multiple objects
Account account = new Account();
Reader<Logger, MoneyTransfer> reader =
lift(account, Account::setLogger )
.map( a -> a.open( "Alice" ) )
.map( a -> a.credit( 300.0 ) )
.flatMap( a -> lift( a.tranfer( 200.0 ), MoneyTransfer::setLogger ) )
.map( MoneyTransfer::execute );
reader.apply( System.out::println );
System.out.println(account + " has balance " + account.getBalance());
Injecting into multiple objects
Account account = new Account();
Reader<Logger, MoneyTransfer> reader =
lift(account, Account::setLogger )
.map( a -> a.open( "Alice" ) )
.map( a -> a.credit( 300.0 ) )
.flatMap( a -> lift( a.tranfer( 200.0 ), MoneyTransfer::setLogger ) )
.map( MoneyTransfer::execute );
reader.apply( System.out::println );
System.out.println(account + " has balance " + account.getBalance());
Mario Fusco
Red Hat – Principal Software Engineer
mario.fusco@gmail.com
twitter: @mariofusco
Q A
Thanks … Questions?

More Related Content

What's hot

FP in Java - Project Lambda and beyond
FP in Java - Project Lambda and beyondFP in Java - Project Lambda and beyond
FP in Java - Project Lambda and beyondMario Fusco
 
Java 8-streams-collectors-patterns
Java 8-streams-collectors-patternsJava 8-streams-collectors-patterns
Java 8-streams-collectors-patternsJosé Paumard
 
Java 8 Default Methods
Java 8 Default MethodsJava 8 Default Methods
Java 8 Default MethodsHaim Michael
 
Let's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APILet's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APIMario Fusco
 
Regular Expressions in Java
Regular Expressions in JavaRegular Expressions in Java
Regular Expressions in JavaOblivionWalker
 
A Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIOA Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIOJorge Vásquez
 
Java Garbage Collection - How it works
Java Garbage Collection - How it worksJava Garbage Collection - How it works
Java Garbage Collection - How it worksMindfire Solutions
 
PHP 良好實踐 (Best Practice)
PHP 良好實踐 (Best Practice)PHP 良好實踐 (Best Practice)
PHP 良好實踐 (Best Practice)Win Yu
 
Devoxx France 2023 - Les nouveautés de Java 19 et 20
Devoxx France 2023 - Les nouveautés de Java 19 et 20Devoxx France 2023 - Les nouveautés de Java 19 et 20
Devoxx France 2023 - Les nouveautés de Java 19 et 20Jean-Michel Doudoux
 
Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8RichardWarburton
 
Java: Regular Expression
Java: Regular ExpressionJava: Regular Expression
Java: Regular ExpressionMasudul Haque
 
Reactive Programming for a demanding world: building event-driven and respons...
Reactive Programming for a demanding world: building event-driven and respons...Reactive Programming for a demanding world: building event-driven and respons...
Reactive Programming for a demanding world: building event-driven and respons...Mario Fusco
 
The lazy programmer's guide to writing thousands of tests
The lazy programmer's guide to writing thousands of testsThe lazy programmer's guide to writing thousands of tests
The lazy programmer's guide to writing thousands of testsScott Wlaschin
 

What's hot (20)

Gradle Introduction
Gradle IntroductionGradle Introduction
Gradle Introduction
 
Java 8 Workshop
Java 8 WorkshopJava 8 Workshop
Java 8 Workshop
 
FP in Java - Project Lambda and beyond
FP in Java - Project Lambda and beyondFP in Java - Project Lambda and beyond
FP in Java - Project Lambda and beyond
 
Java 8-streams-collectors-patterns
Java 8-streams-collectors-patternsJava 8-streams-collectors-patterns
Java 8-streams-collectors-patterns
 
Java 8 Default Methods
Java 8 Default MethodsJava 8 Default Methods
Java 8 Default Methods
 
Clean coding-practices
Clean coding-practicesClean coding-practices
Clean coding-practices
 
Java 8 features
Java 8 featuresJava 8 features
Java 8 features
 
Let's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APILet's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java API
 
Regular Expressions in Java
Regular Expressions in JavaRegular Expressions in Java
Regular Expressions in Java
 
A Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIOA Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIO
 
Java Garbage Collection - How it works
Java Garbage Collection - How it worksJava Garbage Collection - How it works
Java Garbage Collection - How it works
 
PHP 良好實踐 (Best Practice)
PHP 良好實踐 (Best Practice)PHP 良好實踐 (Best Practice)
PHP 良好實踐 (Best Practice)
 
Devoxx France 2023 - Les nouveautés de Java 19 et 20
Devoxx France 2023 - Les nouveautés de Java 19 et 20Devoxx France 2023 - Les nouveautés de Java 19 et 20
Devoxx France 2023 - Les nouveautés de Java 19 et 20
 
Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8Pragmatic functional refactoring with java 8
Pragmatic functional refactoring with java 8
 
Java: Regular Expression
Java: Regular ExpressionJava: Regular Expression
Java: Regular Expression
 
Polymorphism
PolymorphismPolymorphism
Polymorphism
 
Java loops
Java loopsJava loops
Java loops
 
Reactive Programming for a demanding world: building event-driven and respons...
Reactive Programming for a demanding world: building event-driven and respons...Reactive Programming for a demanding world: building event-driven and respons...
Reactive Programming for a demanding world: building event-driven and respons...
 
The lazy programmer's guide to writing thousands of tests
The lazy programmer's guide to writing thousands of testsThe lazy programmer's guide to writing thousands of tests
The lazy programmer's guide to writing thousands of tests
 
JavaScript Inheritance
JavaScript InheritanceJavaScript Inheritance
JavaScript Inheritance
 

Similar to Lazy java

Automation Testing theory notes.pptx
Automation Testing theory notes.pptxAutomation Testing theory notes.pptx
Automation Testing theory notes.pptxNileshBorkar12
 
Lambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive CodeLambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive CodeIan Robertson
 
Functional Programming In Java
Functional Programming In JavaFunctional Programming In Java
Functional Programming In JavaAndrei Solntsev
 
object oriented programming java lectures
object oriented programming java lecturesobject oriented programming java lectures
object oriented programming java lecturesMSohaib24
 
New Functional Features of Java 8
New Functional Features of Java 8New Functional Features of Java 8
New Functional Features of Java 8franciscoortin
 
JAVA Tutorial- Do's and Don'ts of Java programming
JAVA Tutorial- Do's and Don'ts of Java programmingJAVA Tutorial- Do's and Don'ts of Java programming
JAVA Tutorial- Do's and Don'ts of Java programmingKeshav Kumar
 
JAVA Tutorial- Do's and Don'ts of Java programming
JAVA Tutorial- Do's and Don'ts of Java programmingJAVA Tutorial- Do's and Don'ts of Java programming
JAVA Tutorial- Do's and Don'ts of Java programmingKeshav Kumar
 
Functions, List and String methods
Functions, List and String methodsFunctions, List and String methods
Functions, List and String methodsPranavSB
 
computer notes - Data Structures - 5
computer notes - Data Structures - 5computer notes - Data Structures - 5
computer notes - Data Structures - 5ecomputernotes
 
Java 8 presentation
Java 8 presentationJava 8 presentation
Java 8 presentationVan Huong
 

Similar to Lazy java (20)

Core java
Core javaCore java
Core java
 
Scala ntnu
Scala ntnuScala ntnu
Scala ntnu
 
Java tut1
Java tut1Java tut1
Java tut1
 
Tutorial java
Tutorial javaTutorial java
Tutorial java
 
Java Tut1
Java Tut1Java Tut1
Java Tut1
 
Java Tutorial
Java TutorialJava Tutorial
Java Tutorial
 
Automation Testing theory notes.pptx
Automation Testing theory notes.pptxAutomation Testing theory notes.pptx
Automation Testing theory notes.pptx
 
Java 8 new features
Java 8 new featuresJava 8 new features
Java 8 new features
 
Lambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive CodeLambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive Code
 
Functional Programming In Java
Functional Programming In JavaFunctional Programming In Java
Functional Programming In Java
 
object oriented programming java lectures
object oriented programming java lecturesobject oriented programming java lectures
object oriented programming java lectures
 
New Functional Features of Java 8
New Functional Features of Java 8New Functional Features of Java 8
New Functional Features of Java 8
 
Wien15 java8
Wien15 java8Wien15 java8
Wien15 java8
 
JAVA Tutorial- Do's and Don'ts of Java programming
JAVA Tutorial- Do's and Don'ts of Java programmingJAVA Tutorial- Do's and Don'ts of Java programming
JAVA Tutorial- Do's and Don'ts of Java programming
 
JAVA Tutorial- Do's and Don'ts of Java programming
JAVA Tutorial- Do's and Don'ts of Java programmingJAVA Tutorial- Do's and Don'ts of Java programming
JAVA Tutorial- Do's and Don'ts of Java programming
 
java8
java8java8
java8
 
Functions, List and String methods
Functions, List and String methodsFunctions, List and String methods
Functions, List and String methods
 
computer notes - Data Structures - 5
computer notes - Data Structures - 5computer notes - Data Structures - 5
computer notes - Data Structures - 5
 
Java 8 presentation
Java 8 presentationJava 8 presentation
Java 8 presentation
 
Lambda Functions in Java 8
Lambda Functions in Java 8Lambda Functions in Java 8
Lambda Functions in Java 8
 

More from Mario Fusco

Kogito: cloud native business automation
Kogito: cloud native business automationKogito: cloud native business automation
Kogito: cloud native business automationMario Fusco
 
How and why I turned my old Java projects into a first-class serverless compo...
How and why I turned my old Java projects into a first-class serverless compo...How and why I turned my old Java projects into a first-class serverless compo...
How and why I turned my old Java projects into a first-class serverless compo...Mario Fusco
 
Drools 6 deep dive
Drools 6 deep diveDrools 6 deep dive
Drools 6 deep diveMario Fusco
 
OOP and FP - Become a Better Programmer
OOP and FP - Become a Better ProgrammerOOP and FP - Become a Better Programmer
OOP and FP - Become a Better ProgrammerMario Fusco
 
Comparing different concurrency models on the JVM
Comparing different concurrency models on the JVMComparing different concurrency models on the JVM
Comparing different concurrency models on the JVMMario Fusco
 
Why we cannot ignore Functional Programming
Why we cannot ignore Functional ProgrammingWhy we cannot ignore Functional Programming
Why we cannot ignore Functional ProgrammingMario Fusco
 
Real world DSL - making technical and business people speaking the same language
Real world DSL - making technical and business people speaking the same languageReal world DSL - making technical and business people speaking the same language
Real world DSL - making technical and business people speaking the same languageMario Fusco
 
Introducing Drools
Introducing DroolsIntroducing Drools
Introducing DroolsMario Fusco
 
Java 7, 8 & 9 - Moving the language forward
Java 7, 8 & 9 - Moving the language forwardJava 7, 8 & 9 - Moving the language forward
Java 7, 8 & 9 - Moving the language forwardMario Fusco
 
Swiss army knife Spring
Swiss army knife SpringSwiss army knife Spring
Swiss army knife SpringMario Fusco
 
No more loops with lambdaj
No more loops with lambdajNo more loops with lambdaj
No more loops with lambdajMario Fusco
 
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STMConcurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STMMario Fusco
 
Scala - where objects and functions meet
Scala - where objects and functions meetScala - where objects and functions meet
Scala - where objects and functions meetMario Fusco
 

More from Mario Fusco (15)

Kogito: cloud native business automation
Kogito: cloud native business automationKogito: cloud native business automation
Kogito: cloud native business automation
 
How and why I turned my old Java projects into a first-class serverless compo...
How and why I turned my old Java projects into a first-class serverless compo...How and why I turned my old Java projects into a first-class serverless compo...
How and why I turned my old Java projects into a first-class serverless compo...
 
OOP and FP
OOP and FPOOP and FP
OOP and FP
 
Drools 6 deep dive
Drools 6 deep diveDrools 6 deep dive
Drools 6 deep dive
 
OOP and FP - Become a Better Programmer
OOP and FP - Become a Better ProgrammerOOP and FP - Become a Better Programmer
OOP and FP - Become a Better Programmer
 
Comparing different concurrency models on the JVM
Comparing different concurrency models on the JVMComparing different concurrency models on the JVM
Comparing different concurrency models on the JVM
 
Why we cannot ignore Functional Programming
Why we cannot ignore Functional ProgrammingWhy we cannot ignore Functional Programming
Why we cannot ignore Functional Programming
 
Real world DSL - making technical and business people speaking the same language
Real world DSL - making technical and business people speaking the same languageReal world DSL - making technical and business people speaking the same language
Real world DSL - making technical and business people speaking the same language
 
Introducing Drools
Introducing DroolsIntroducing Drools
Introducing Drools
 
Java 7, 8 & 9 - Moving the language forward
Java 7, 8 & 9 - Moving the language forwardJava 7, 8 & 9 - Moving the language forward
Java 7, 8 & 9 - Moving the language forward
 
Hammurabi
HammurabiHammurabi
Hammurabi
 
Swiss army knife Spring
Swiss army knife SpringSwiss army knife Spring
Swiss army knife Spring
 
No more loops with lambdaj
No more loops with lambdajNo more loops with lambdaj
No more loops with lambdaj
 
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STMConcurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
Concurrency, Scalability & Fault-tolerance 2.0 with Akka Actors & STM
 
Scala - where objects and functions meet
Scala - where objects and functions meetScala - where objects and functions meet
Scala - where objects and functions meet
 

Recently uploaded

Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalLionel Briand
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxAndreas Kunz
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZABSYZ Inc
 
Best Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh ITBest Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh ITmanoharjgpsolutions
 
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4j
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4jGraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4j
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4jNeo4j
 
Effectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryErrorEffectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryErrorTier1 app
 
Post Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on IdentityPost Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on Identityteam-WIBU
 
Introduction to Firebase Workshop Slides
Introduction to Firebase Workshop SlidesIntroduction to Firebase Workshop Slides
Introduction to Firebase Workshop Slidesvaideheekore1
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogueitservices996
 
Patterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencePatterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencessuser9e7c64
 
VictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News UpdateVictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News UpdateVictoriaMetrics
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsChristian Birchler
 
Large Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and RepairLarge Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and RepairLionel Briand
 
Amazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesAmazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesKrzysztofKkol1
 
Zer0con 2024 final share short version.pdf
Zer0con 2024 final share short version.pdfZer0con 2024 final share short version.pdf
Zer0con 2024 final share short version.pdfmaor17
 
Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Rob Geurden
 
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...Bert Jan Schrijver
 
2024 DevNexus Patterns for Resiliency: Shuffle shards
2024 DevNexus Patterns for Resiliency: Shuffle shards2024 DevNexus Patterns for Resiliency: Shuffle shards
2024 DevNexus Patterns for Resiliency: Shuffle shardsChristopher Curtin
 
What’s New in VictoriaMetrics: Q1 2024 Updates
What’s New in VictoriaMetrics: Q1 2024 UpdatesWhat’s New in VictoriaMetrics: Q1 2024 Updates
What’s New in VictoriaMetrics: Q1 2024 UpdatesVictoriaMetrics
 
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...OnePlan Solutions
 

Recently uploaded (20)

Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive Goal
 
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZ
 
Best Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh ITBest Angular 17 Classroom & Online training - Naresh IT
Best Angular 17 Classroom & Online training - Naresh IT
 
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4j
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4jGraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4j
GraphSummit Madrid - Product Vision and Roadmap - Luis Salvador Neo4j
 
Effectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryErrorEffectively Troubleshoot 9 Types of OutOfMemoryError
Effectively Troubleshoot 9 Types of OutOfMemoryError
 
Post Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on IdentityPost Quantum Cryptography – The Impact on Identity
Post Quantum Cryptography – The Impact on Identity
 
Introduction to Firebase Workshop Slides
Introduction to Firebase Workshop SlidesIntroduction to Firebase Workshop Slides
Introduction to Firebase Workshop Slides
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogue
 
Patterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencePatterns for automating API delivery. API conference
Patterns for automating API delivery. API conference
 
VictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News UpdateVictoriaMetrics Q1 Meet Up '24 - Community & News Update
VictoriaMetrics Q1 Meet Up '24 - Community & News Update
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
 
Large Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and RepairLarge Language Models for Test Case Evolution and Repair
Large Language Models for Test Case Evolution and Repair
 
Amazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesAmazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilities
 
Zer0con 2024 final share short version.pdf
Zer0con 2024 final share short version.pdfZer0con 2024 final share short version.pdf
Zer0con 2024 final share short version.pdf
 
Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...
 
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
JavaLand 2024 - Going serverless with Quarkus GraalVM native images and AWS L...
 
2024 DevNexus Patterns for Resiliency: Shuffle shards
2024 DevNexus Patterns for Resiliency: Shuffle shards2024 DevNexus Patterns for Resiliency: Shuffle shards
2024 DevNexus Patterns for Resiliency: Shuffle shards
 
What’s New in VictoriaMetrics: Q1 2024 Updates
What’s New in VictoriaMetrics: Q1 2024 UpdatesWhat’s New in VictoriaMetrics: Q1 2024 Updates
What’s New in VictoriaMetrics: Q1 2024 Updates
 
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
 

Lazy java

  • 1. λazy by Mario Fusco Red Hat – Principal Software Engineer @mariofusco
  • 2. Lazy Evaluation Lazy evaluation (or call-by-name) is an evaluation strategy which delays the evaluation of an expression until its value is needed I know what to do. Wake me up when you really need it
  • 3. Strictness vs. Laziness Strictness is a property of functions (or methods in Java). A strict function always evaluates its arguments as soon as they’re passed to it. Conversely a lazy function may choose not to evaluate one or more of its arguments and in general it will evaluate them only when they’re actually needed. To recap, strictness is about doing things, laziness is about noting things to do.
  • 4. Java is a strict language ... … with some notable (and unavoidable) exceptions ✔ Boolean operators || and && ✔ Ternary operator ? : ✔ if ... else ✔ for/while loops ✔ Java 8 streams Q: Can exist a totally strict language?
  • 5. Java is a strict language ... … with some notable (and unavoidable) exceptions ✔ Boolean operators || and && ✔ Ternary operator ? : ✔ if ... else ✔ for/while loops ✔ Java 8 streams Q: Can exist a totally strict language? A: It’s hard if not impossible to imagine how it could work boolean isAdult = person != null && person.getAge() >= 18;
  • 6. Turning Java into a lazy language <T> T ternary(boolean pred, T first, T second) { if (pred) { return first; } else { return second; } } String val1() { return "first"; } String val2() { return "second"; } String result1 = bool ? val1() : val2(); String result2 = ternary(bool, val1(), val2());
  • 7. Turning Java into a lazy language <T> T ternary(boolean pred, Supplier<T> first, Supplier<T> second) { if (pred) { return first.get(); } else { return second.get(); } } String val1() { return "first"; } String val2() { return "second"; } String result1 = bool ? val1() : val2(); String result2 = ternary(bool, () -> val1(), () -> val2());
  • 8. A simple practical example: logging // pre-Java 8 style optimization if (logger.isTraceEnabled()) { logger.trace("Some long-running operation returned {}", expensiveOperation()); }
  • 9. A simple practical example: logging // pre-Java 8 style optimization if (logger.isTraceEnabled()) { logger.trace("Some long-running operation returned {}", expensiveOperation()); } // Java 8 style optimization using laziness logger.trace("Some long-running operation returned {}", () -> expensiveOperation()); * from Apache Log4J 2 docs no need to explicitly check the log level: the lambda expression is not evaluated if the TRACE level is not enabled *
  • 10. Laziness: the ultimate performance optimization technique Performance optimization pro tip: before trying to inline/optimize/parallelize a piece of code, ask yourself if you could avoid to run it at all. Laziness is probably the only form of performance optimization which is (almost) never premature There is nothing so useless as doing efficiently something that should not be done at all
  • 11. The case of Java 8 Streams IntStream.iterate( 1, i -> i+1 ) .map( i -> i * 2 ) .filter( i -> i > 5 ) .findFirst();
  • 12. The case of Java 8 Streams IntStream.iterate( 1, i -> i+1 ) .map( i -> i * 2 ) .filter( i -> i > 5 ) .findFirst(); Thank to laziness the Stream can be potentially infinite
  • 13. The case of Java 8 Streams IntStream.iterate( 1, i -> i+1 ) .map( i -> i * 2 ) .filter( i -> i > 5 ) .findFirst(); Thank to laziness the Stream can be potentially infinite Intermediate operations are lazy: they don’t perform any action until a terminal operation is reached
  • 14. The case of Java 8 Streams IntStream.iterate( 1, i -> i+1 ) .map( i -> i * 2 ) .filter( i -> i > 5 ) .findFirst(); Thank to laziness the Stream can be potentially infinite Intermediate operations are lazy: they don’t perform any action until a terminal operation is reached Only the terminal operation triggers the pipeline of computations A Stream is not a data structure. It is the lazy specification of a how to manipulate a set of data.
  • 15. Things you can’t do without laziness There are several algorithms that can’t be (reasonably) implemented without laziness. For example let’s consider the following: 1. Take the list of positive integers. 2. Filter the primes. 3. Return the list of the first ten results.
  • 16. Wait! I can achieve the same with a strict algorithm Yes, but how? 1. Take the first integer. 2. Check whether it’s a prime. 3. If it is, store it in a list. 4. Check whether the list has ten elements. 5. If it has ten elements, return it as the result. 6. If not, increment the integer by 1. 7. Go to line 2.
  • 17. Wait! I can achieve the same with a strict algorithm Yes, but how? 1. Take the first integer. 2. Check whether it’s a prime. 3. If it is, store it in a list. 4. Check whether the list has ten elements. 5. If it has ten elements, return it as the result. 6. If not, increment the integer by 1. 7. Go to line 2. Sure, doable … but what a mess!
  • 18. Laziness lets us separate the description of an expression from the evaluation of that expression Laziness is an enabler for separation of concerns
  • 19. List<String> errors = Files.lines(Paths.get(fileName)) .filter(l -> l.startsWith("ERROR")) .limit(40) .collect(toList()); Separation of Concerns List<String> errors = new ArrayList<>(); int errorCount = 0; File file = new File(fileName); String line = file.readLine(); while (errorCount < 40 && line != null) { if (line.startsWith("ERROR")) { errors.add(line); errorCount++; } line = file.readLine(); }
  • 20. Cool! Now I know: I will use a Stream also for prime numbers
  • 21. Cool! Now I know: I will use a Stream also for prime numbers Let’s give this a try ...
  • 22. Creating a Stream of prime numbers public IntStream primes(int n) { return IntStream.iterate(2, i -> i + 1) .filter(this::isPrime) .limit(n); } public boolean isPrime(int candidate) { return IntStream.rangeClosed(2, (int)Math.sqrt(candidate)) .noneMatch(i -> candidate % i == 0); }
  • 23. Creating a Stream of prime numbers public IntStream primes(int n) { return IntStream.iterate(2, i -> i + 1) .filter(this::isPrime) .limit(n); } public boolean isPrime(int candidate) { return IntStream.rangeClosed(2, (int)Math.sqrt(candidate)) .noneMatch(i -> candidate % i == 0); } It iterates through every number every time to see if it can be exactly divided by a candidate number, but it would be enough to only test numbers that have been already classified as prime Inefficient
  • 24. Recursively creating a Stream of primes static Intstream numbers() { return IntStream.iterate(2, n -> n + 1); } static int head(IntStream numbers) { return numbers.findFirst().getAsInt(); } static IntStream tail(IntStream numbers) { return numbers.skip(1); } static IntStream primes(IntStream numbers) { int head = head(numbers()); return IntStream.concat( IntStream.of(head), primes(tail(numbers).filter(n -> n % head != 0)) ); } Cannot invoke 2 terminal operations on the same Streams Problems? No lazy evaluation in Java leads to an endless recursion
  • 25. Lazy evaluation in Scala def numbers(n: Int): Stream[Int] = n #:: numbers(n+1) def primes(numbers: Stream[Int]): Stream[Int] = numbers.head #:: primes(numbers.tail filter (n -> n % numbers.head != 0)) lazy concatenation In Scala the #:: method (lazy concatenation) returns immediately and the elements are evaluated only when needed
  • 26. interface HeadTailList<T> { T head(); HeadTailList<T> tail(); boolean isEmpty(); HeadTailList<T> filter(Predicate<T> p); } Implementing a lazy list in Java class LazyList<T> implements HeadTailList<T> { private final T head; private final Supplier<HeadTailList<T>> tail; public LazyList(T head, Supplier<HeadTailList<T>> tail) { this.head = head; this.tail = tail; } public T head() { return head; } public HeadTailList<T> tail() { return tail.get(); } public boolean isEmpty() { return false; } }
  • 27. … and its lazy filter class LazyList<T> implements HeadTailList<T> { ... public HeadTailList<T> filter(Predicate<T> p) { return isEmpty() ? this : p.test(head()) ? new LazyList<>(head(), () -> tail().filter(p)) : tail().filter(p); } } 2 3 4 5 6 7 8 9 2 3 5 7
  • 28. Back to generating primes static HeadTailList<Integer> primes(HeadTailList<Integer> numbers) { return new LazyList<>( numbers.head(), () -> primes(numbers.tail() .filter(n -> n % numbers.head() != 0))); } static LazyList<Integer> from(int n) { return new LazyList<Integer>(n, () -> from(n+1)); }
  • 29. Back to generating primes static HeadTailList<Integer> primes(HeadTailList<Integer> numbers) { return new LazyList<>( numbers.head(), () -> primes(numbers.tail() .filter(n -> n % numbers.head() != 0))); } static LazyList<Integer> from(int n) { return new LazyList<Integer>(n, () -> from(n+1)); } LazyList<Integer> numbers = from(2); int two = primes(numbers).head(); int three = primes(numbers).tail().head(); int five = primes(numbers).tail().tail().head();
  • 30. LazyList of primes under the hood from(2) → 2 () -> from(3) 2 () -> primes( from(3).filter(2) )primes(from(2)) →
  • 31. LazyList of primes under the hood from(2) → 2 () -> from(3) 2 () -> primes( from(3).filter(2) ) 3 () -> from(4).filter(2).filter(3)() -> primes( ) 3 () -> primes( from(4).filter(2).filter(3) ) primes(from(2)) → .tail() →
  • 32. LazyList of primes under the hood from(2) → 2 () -> from(3) 2 () -> primes( from(3).filter(2) ) 3 () -> from(4).filter(2).filter(3)() -> primes( ) 3 () -> primes( from(4).filter(2).filter(3) ) 5 () -> from(6).filter(2).filter(3).filter(5)() -> primes( ) 5 () -> primes( from(6).filter(2).filter(3).filter(5) ) primes(from(2)) → .tail() → .tail() →
  • 33. Printing primes static <T> void printAll(HeadTailList<T> list) { while (!list.isEmpty()){ System.out.println(list.head()); list = list.tail(); } } printAll(primes(from(2))); iteratively
  • 34. Printing primes static <T> void printAll(HeadTailList<T> list) { while (!list.isEmpty()){ System.out.println(list.head()); list = list.tail(); } } printAll(primes(from(2))); static <T> void printAll(HeadTailList<T> list) { if (list.isEmpty()) return; System.out.println(list.head()); printAll(list.tail()); } printAll(primes(from(2))); iteratively recursively
  • 35. Iteration vs. Recursion External Iteration public int sumAll(int n) { int result = 0; for (int i = 0; i <= n; i++) { result += i; } return result; } Internal Iteration public static int sumAll(int n) { return IntStream.rangeClosed(0, n).sum(); }
  • 36. Iteration vs. Recursion External Iteration public int sumAll(int n) { int result = 0; for (int i = 0; i <= n; i++) { result += i; } return result; } Recursion public int sumAll(int n) { return n == 0 ? 0 : n + sumAll(n - 1); } Internal Iteration public static int sumAll(int n) { return IntStream.rangeClosed(0, n).sum(); }
  • 37. public class PalindromePredicate implements Predicate<String> { @Override public boolean test(String s) { return isPalindrome(s, 0, s.length()-1); } private boolean isPalindrome(String s, int start, int end) { while (start < end && !isLetter(s.charAt(start))) start++; while (start < end && !isLetter(s.charAt(end))) end--; if (start >= end) return true; if (toLowerCase(s.charAt(start)) != toLowerCase(s.charAt(end))) return false; return isPalindrome(s, start+1, end-1); } } Another Recursive Example Tail Rescursive Call
  • 38. What's the problem? List<String> sentences = asList( "Dammit, I’m mad!", "Rise to vote, sir!", "Never odd or even", "Never odd and even", "Was it a car or a cat I saw?", "Was it a car or a dog I saw?", VERY_LONG_PALINDROME ); sentences.stream() .filter(new PalindromePredicate()) .forEach(System.out::println);
  • 39. What's the problem? List<String> sentences = asList( "Dammit, I’m mad!", "Rise to vote, sir!", "Never odd or even", "Never odd and even", "Was it a car or a cat I saw?", "Was it a car or a dog I saw?", VERY_LONG_PALINDROME ); sentences.stream() .filter(new PalindromePredicate()) .forEach(System.out::println); Exception in thread "main" java.lang.StackOverflowError at java.lang.Character.getType(Character.java:6924) at java.lang.Character.isLetter(Character.java:5798) at java.lang.Character.isLetter(Character.java:5761) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:17) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21) at org.javaz.trampoline.PalindromePredicate.isPalindrome(PalindromePredicate.java:21) ……..
  • 40. Tail Call Optimization int func_a(int data) { data = do_this(data); return do_that(data); } ... | executing inside func_a() push EIP | push current instruction pointer on stack push data | push variable 'data' on the stack jmp do_this | call do_this() by jumping to its address ... | executing inside do_this() push EIP | push current instruction pointer on stack push data | push variable 'data' on the stack jmp do_that | call do_that() by jumping to its address ... | executing inside do_that() pop data | prepare to return value of 'data' pop EIP | return to do_this() pop data | prepare to return value of 'data' pop EIP | return to func_a() pop data | prepare to return value of 'data' pop EIP | return to func_a() caller ... caller
  • 41. Tail Call Optimization int func_a(int data) { data = do_this(data); return do_that(data); } ... | executing inside func_a() push EIP | push current instruction pointer on stack push data | push variable 'data' on the stack jmp do_this | call do_this() by jumping to its address ... | executing inside do_this() push EIP | push current instruction pointer on stack push data | push variable 'data' on the stack jmp do_that | call do_that() by jumping to its address ... | executing inside do_that() pop data | prepare to return value of 'data' pop EIP | return to do_this() pop data | prepare to return value of 'data' pop EIP | return to func_a() pop data | prepare to return value of 'data' pop EIP | return to func_a() caller ... caller avoid putting instruction on stack
  • 42. from Recursion to Tail Recursion Recursion public int sumAll(int n) { return n == 0 ? 0 : n + sumAll(n - 1); }
  • 43. from Recursion to Tail Recursion Recursion public int sumAll(int n) { return n == 0 ? 0 : n + sumAll(n - 1); } Tail Recursion public int sumAll(int n) { return sumAll(n, 0); } private int sumAll(int n, int acc) { return n == 0 ? acc : sumAll(n – 1, acc + n); }
  • 44. Tail Recursion in Scala def isPalindrome(s: String): Boolean = isPalindrome(s, 0, s.length-1) @tailrec def isPalindrome(s: String, start: Int, end: Int): Boolean = { val pos1 = nextLetter(s, start, end) val pos2 = prevLetter(s, start, end) if (pos1 >= pos2) return true if (toLowerCase(s.charAt(pos1)) != toLowerCase(s.charAt(pos2))) return false isPalindrome(s, pos1+1, pos2-1) } @tailrec def nextLetter(s: String, start: Int, end: Int): Int = if (start > end || isLetter(s.charAt(start))) start else nextLetter(s, start+1, end) @tailrec def prevLetter(s: String, start: Int, end: Int): Int = if (start > end || isLetter(s.charAt(end))) end else prevLetter(s, start, end-1)
  • 45. Tail Recursion in Java? Scala (and many other functional languages) automatically perform tail call optimization at compile time @tailrec annotation ensures the compiler will optimize a tail recursive function (i.e. you will get a compilation failure if you use it on a function that is not really tail recursive) Java compiler doesn't perform any tail call optimization (and very likely won't do it in a near future) How can we overcome this limitation and have StackOverflowError-free functions also in Java tail recursive methods?
  • 46. Trampolines to the rescue A trampoline is an iteration applying a list of functions. Each function returns the next function for the loop to run. Func1 return apply Func2 return apply Func3 return apply FuncN apply … result return
  • 47. Implementing the TailCall … @FunctionalInterface public interface TailCall<T> extends Supplier<TailCall<T>> { default boolean isComplete() { return false; } default T result() { throw new UnsupportedOperationException(); } default T invoke() { return Stream.iterate(this, TailCall::get) .filter(TailCall::isComplete) .findFirst() .get() .result(); } static <T> TailCall<T> done(T result) { return new TerminalCall<T>(result); } }
  • 48. … and the terminal TailCall public class TerminalCall<T> implements TailCall<T> { private final T result; public TerminalCall( T result ) { this.result = result; } @Override public boolean isComplete() { return true; } @Override public T result() { return result; } @Override public TailCall<T> get() { throw new UnsupportedOperationException(); } }
  • 49. Using the Trampoline public class PalindromePredicate implements Predicate<String> { @Override public boolean test(String s) { return isPalindrome(s, 0, s.length()-1).invoke(); } private TailCall<Boolean> isPalindrome(String s, int start, int end) { while (start < end && !isLetter(s.charAt(start))) start++; while (end > start && !isLetter(s.charAt(end))) end--; if (start >= end) return done(true); if (toLowerCase(s.charAt(start)) != toLowerCase(s.charAt(end))) return done(false); int newStart = start + 1; int newEnd = end - 1; return () -> isPalindrome(s, newStart, newEnd); } }
  • 50. What else laziness can do for us? Avoiding eager dependency injection by lazily providing arguments to computation only when they are needed i.e. Introducing the Reader Monad
  • 51. What’s wrong with annotation- based dependency injection? ➢ Eager in nature ➢ “new” keyword is forbidden ➢ All-your-beans-belong-to-us syndrome ➢ Complicated objects lifecycle ➢ Depending on scope may not work well with threads ➢ Hard to debug if something goes wrong ➢ Easy to abuse leading to broken encapsulation
  • 52. Annotation based dependency injection transforms what should be a compile time problem into a runtime one (often hard to debug)
  • 53. Introducing the Reader monad ... public class Reader<R, A> { private final Function<R, A> exec; public Reader( Function<R, A> exec ) { this.exec = exec; } public <B> Reader<R, B> map(Function<A, B> f) { return new Reader<>( exec.andThen(f) ); } public <B> Reader<R, B> flatMap(Function<A, Reader<R, B>> f) { return new Reader<>( r -> exec.andThen(f).apply(r).apply(r) ); } public A apply(R r) { return exec.apply( r ); } } The reader monad provides an environment to wrap an abstract computation without evaluating it
  • 54. The Reader Monad The Reader monad makes a lazy computation explicit in the type system, while hiding the logic to apply it In other words the reader monad allows us to treat functions as values with a context We can act as if we already know what the functions will return.
  • 55. @FunctionalInterface public interface Logger extends Consumer<String> { } public class Account { private Logger logger; private String owner; private double balance; public Account open( String owner ) { this.owner = owner; logger.accept( "Account opened by " + owner ); return this; } public Account credit( double value ) { balance += value; logger.accept( "Credited " + value + " to " + owner ); return this; } public Account debit( double value ) { balance -= value; logger.accept( "Debited " + value + " to " + owner ); return this; } public double getBalance() { return balance; } public void setLogger( Logger logger ) { this.logger = logger; } } Usually injected
  • 56. Account account = new Account(); account.open( "Alice" ) .credit( 200.0 ) .credit( 300.0 ) .debit( 400.0 ); The joys of dependency injection
  • 57. Account account = new Account(); account.open( "Alice" ) .credit( 200.0 ) .credit( 300.0 ) .debit( 400.0 ); Throws NPE if for some reason the logger couldn’t be injected The joys of dependency injection :( You should never use “new”
  • 58. public static <R, A> Reader<R, A> lift( A obj, BiConsumer<A, R> injector ) { return new Reader<>( r -> { injector.accept( obj, r ); return obj; } ); } Lazy injection with the reader monad
  • 59. public static <R, A> Reader<R, A> lift( A obj, BiConsumer<A, R> injector ) { return new Reader<>( r -> { injector.accept( obj, r ); return obj; } ); } Lazy injection with the reader monad Account account = new Account(); Reader<Logger, Account> reader = lift(account, Account::setLogger ) .map( a -> a.open( "Alice" ) ) .map( a -> a.credit( 200.0 ) ) .map( a -> a.credit( 300.0 ) ) .map( a -> a.debit( 400.0 ) ); reader.apply( System.out::println ); System.out.println(account + " has balance " + account.getBalance());
  • 60. public static <R, A> Reader<R, A> lift( A obj, BiConsumer<A, R> injector ) { return new Reader<>( r -> { injector.accept( obj, r ); return obj; } ); } Lazy injection with the reader monad Account account = new Account(); Reader<Logger, Account> reader = lift(account, Account::setLogger ) .map( a -> a.open( "Alice" ) ) .map( a -> a.credit( 200.0 ) ) .map( a -> a.credit( 300.0 ) ) .map( a -> a.debit( 400.0 ) ); reader.apply( System.out::println ); System.out.println(account + " has balance " + account.getBalance());
  • 61. Replacing injection with function application Account Function<Logger, Account> Reader Function<Logger, Account> Reader Function<Logger, Account> Reader Function<Logger, Account> Reader Function<Logger, Account> Reader lift apply(logger) account map(Function<Account, Account>) map(Function<Account, Account>)
  • 62. Function based injection Account account = new Account(); Function<Logger, Account> inject = l -> { account.setLogger( l ); return account; }; Function<Logger, Account> f = inject .andThen( a -> a.open( "Alice" ) ) .andThen( a -> a.credit( 200.0 ) ) .andThen( a -> a.credit( 300.0 ) ) .andThen( a -> a.debit( 400.0 ) ); f.apply( System.out::println ); System.out.println(account + " has balance " + account.getBalance()); The reader monad provides a more structured and powerful approach. In this simple case a simple function composition is enough to achieve the same result.
  • 63. public class Account { ... public MoneyTransfer tranfer( double value ) { return new MoneyTransfer( this, value ); } } public class MoneyTransfer { private Logger logger; private final Account account; private final double amount; public MoneyTransfer( Account account, double amount ) { this.account = account; this.amount = amount; } public void setLogger( Logger logger ) { this.logger = logger; } public MoneyTransfer execute() { account.debit( amount ); logger.accept( "Transferred " + amount + " from " + account ); return this; } } Injecting into multiple objects
  • 64. Injecting into multiple objects Account account = new Account(); Reader<Logger, MoneyTransfer> reader = lift(account, Account::setLogger ) .map( a -> a.open( "Alice" ) ) .map( a -> a.credit( 300.0 ) ) .flatMap( a -> lift( a.tranfer( 200.0 ), MoneyTransfer::setLogger ) ) .map( MoneyTransfer::execute ); reader.apply( System.out::println ); System.out.println(account + " has balance " + account.getBalance());
  • 65. Injecting into multiple objects Account account = new Account(); Reader<Logger, MoneyTransfer> reader = lift(account, Account::setLogger ) .map( a -> a.open( "Alice" ) ) .map( a -> a.credit( 300.0 ) ) .flatMap( a -> lift( a.tranfer( 200.0 ), MoneyTransfer::setLogger ) ) .map( MoneyTransfer::execute ); reader.apply( System.out::println ); System.out.println(account + " has balance " + account.getBalance());
  • 66. Mario Fusco Red Hat – Principal Software Engineer mario.fusco@gmail.com twitter: @mariofusco Q A Thanks … Questions?