dtonhofer
Functional Programming in Java, Second Edition: All the code for Chapter 8, "Using Tail-Call Optimization", in one class
All the code for Chapter 8, Using Tail-Call Optimization, in one class. But without the part where we later need to “fix the arithmetic overflow”, an ancillary problem that is solely due to the fact that the example recursive function we use is the factorial. Here we use a simple recursive sum instead.
package chapter8.tailcalls;
import org.junit.jupiter.api.Test;
import java.util.function.Function;
import java.util.stream.Stream;
public class Chapter8_TailCallOptimization {
private final static int limit = 70000;
// ---
// The "very slick stack simulator" (VSSS) for optimizable (not necessarily recursive) tail calls
// This is the code "recur/fpij/TailCall.java" on p.142
// with a fix: the .get() on the Stream has been replaced by .orElseThrow()
// If we only use get(), the linter tells us: "Optional.get() without "isPresent()" check".
// ---
@FunctionalInterface
public interface TailCall<T> {
TailCall<T> apply();
default boolean isComplete() {
return false;
}
default T result() {
throw new Error("not implemented");
}
default T invoke() {
return Stream.iterate(this, TailCall::apply)
.filter(TailCall::isComplete)
.findFirst()
.orElseThrow()
.result();
}
}
// "recur/fpij/TailCalls.java" on p.143
public static class TailCalls {
// call() simply exists so that usage has a symmetric look
// (...but I'm not sure this improves understanding)
public static <T> TailCall<T> call(final TailCall<T> nextCall) {
return nextCall;
}
public static <T> TailCall<T> done(final T value) {
return new TailCall<T>() {
@Override
public boolean isComplete() {
return true;
}
@Override
public T result() {
return value;
}
@Override
public TailCall<T> apply() {
throw new Error("not implemented");
}
};
}
}
// ---
// Summing using a recursive call that is not a tail call (avoid if possible, though
// it is not always possible)
// Replaces "recur/fpij/Factorial.java" on page 140.
// ---
private static class SumRecursivelyNaively {
public static long sum(final int number) {
return (number == 1) ? 1 : (number + sum(number - 1));
}
}
// ---
// Summing using a recursive call that is a proper tail call.
// This approach uses an accumulator while going "down/into" the recursion.
// On the way "back up/outo of" the recursion there are only "returns".
// This can be optimized so that only 1 stack frame is used. However, the Java compiler
// (and/or the JVM?) does not do that fully (maybe because it needs to keep track of
// stack frames for debugging?), so we still get stack overflow after some time.
// ---
private static class SumRecursivelyUsingTailCalls {
public static long sum(final int number) {
return sum_inner(1, number);
}
private static long sum_inner(final long accumulator, final int number) {
if (number == 1) {
return accumulator;
} else {
return sum_inner(accumulator + number, number - 1);
}
}
}
// ---
// An application of VSSS - the very sly stack simulator
// Replaces "recur/fpij/Factorial.java" on p.141
// ---
private static class SumRecursivelyWithVSSS {
public static long sum(final int number) {
return sum_inner(1, number).invoke();
}
// Note that this method is *never* called recursively!
public static TailCall<Long> sum_inner(final long accumulator, final int number) {
if (number == 1) {
// Return the "TailCalls" instance that ends the stream
return TailCalls.done(accumulator);
} else {
// "call()" does nothing, and we could just return the closure directly, but it looks nice
return TailCalls.call(
// When called by Stream.iterate(), this closure is supposed to generate&return the
// "next TailCall instance" of the stream
() -> sum_inner(accumulator + number, number - 1)
);
}
}
}
// === TESTING SUPPORT BEGINS ===
private static boolean callSum(final boolean skip, final String name, int n, Function<Integer, Long> sum) {
boolean skipNextTime = skip;
if (!skip) {
try {
long res = sum.apply(n);
// Properly, this test should be in the caller
if (n == limit - 1) {
System.out.println("Reached the end: " + name + "(" + n + ") = " + res);
}
} catch (StackOverflowError err) {
System.out.println("Stack overflow for " + name + "(" + n + ")");
skipNextTime = true;
}
}
return skipNextTime;
}
// Running the three approaches at summing recursively till stack overflow occurs!
// Works best if one reduces the maximum stack size of the JVM,
// options "-Xss1m" or "-XX:ThreadStackSize=1024" (the latter in KiB)
// See https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html#advanced-runtime-options-for-java
//
// Example output:
//
// Stack overflow for SumRecursivelyNaively.sum(38919)
// Stack overflow for SumRecursivelyUsingTailCalls.sum(58375)
// Reached the end: SumRecursivelyWithVSSS.sum(69999) = 2449965000
@Test
public void runThem() {
boolean skipNaiveVersion = false;
boolean skipTailCallVersion = false;
boolean skipVsssVersion = false;
for (int n = 1; n < limit && !(skipTailCallVersion && skipNaiveVersion && skipVsssVersion); n += 1) {
skipNaiveVersion = callSum(skipNaiveVersion, "SumRecursivelyNaively.sum", n, SumRecursivelyNaively::sum);
skipTailCallVersion = callSum(skipTailCallVersion, "SumRecursivelyUsingTailCalls.sum", n, SumRecursivelyUsingTailCalls::sum);
skipVsssVersion = callSum(skipVsssVersion, "SumRecursivelyWithVSSS.sum", n, SumRecursivelyWithVSSS::sum);
}
}
}
Popular Pragmatic Bookshelf topics
This test is broken right out of the box…
— FAIL: TestAgent (7.82s)
agent_test.go:77:
Error Trace: agent_test.go:77
agent_test.go:...
New
Hi @Margaret ,
On page VII the book tells us the example and snippets will be all using Elixir version 1.11
But on page 3 almost the en...
New
On the page xv there is an instruction to run bin/setup from the main folder. I downloaded the source code today (12/03/21) and can’t see...
New
Title: Intuitive Python: docker run… denied error (page 2)
Attempted to run the docker command in both CLI and Powershell
PS C:\Users\r...
New
I think I might have found a problem involving SwitchCompat, thumbTint, and trackTint.
As entered, the SwitchCompat changes color to hol...
New
Title: Build a Weather Station with Elixir and Nerves: Problem connecting to Postgres with Grafana on (page 64)
If you follow the defau...
New
The markup used to display the uploaded image results in a Phoenix.LiveView.HTMLTokenizer.ParseError error.
lib/pento_web/live/product_l...
New
I’m a newbie to Rails 7 and have hit an issue with the bin/Dev script mentioned on pages 112-113.
Iteration A1 - Seeing the list of prod...
New
Hi, I’ve got a question about the implementation of PubSub when using a Phoenix.Socket.Transport behaviour rather than channels.
Before ...
New
AWDWR 7, page 152, page 153:
Hello everyone,
I’m a little bit lost on the hotwire part. I didn’t fully understand it.
On page 152 @rub...
New
Other popular topics
Algorithms and data structures are much more than abstract concepts. Mastering them enables you to write code that runs faster and more e...
New
No chair. I have a standing desk.
This post was split into a dedicated thread from our thread about chairs :slight_smile:
New
I am asking for any distro that only has the bare-bones to be able to get a shell in the server and then just install the packages as we ...
New
Oh just spent so much time on this to discover now that RancherOS is in end of life but Rancher is refusing to mark the Github repo as su...
New
Author Spotlight
Dmitry Zinoviev
@aqsaqal
Today we’re putting our spotlight on Dmitry Zinoviev, author of Data Science Essentials in ...
New
Author Spotlight
Erin Dees
@undees
Welcome to our new author spotlight! We had the pleasure of chatting with Erin Dees, co-author of ...
New
Will Swifties’ war on AI fakes spark a deepfake porn reckoning?
New
Get the comprehensive, insider information you need for Rails 8 with the new edition of this award-winning classic.
Sam Ruby @rubys
...
New
This is a very quick guide, you just need to:
Download LM Studio: https://lmstudio.ai/
Click on search
Type DeepSeek, then select the o...
New
Curious what kind of results others are getting, I think actually prefer the 7B model to the 32B model, not only is it faster but the qua...
New
Categories:
Sub Categories:
Popular Portals
- /elixir
- /rust
- /wasm
- /ruby
- /erlang
- /phoenix
- /keyboards
- /python
- /js
- /rails
- /security
- /go
- /swift
- /vim
- /clojure
- /java
- /emacs
- /haskell
- /typescript
- /svelte
- /onivim
- /kotlin
- /c-plus-plus
- /crystal
- /tailwind
- /react
- /gleam
- /ocaml
- /elm
- /vscode
- /flutter
- /ash
- /html
- /deepseek
- /opensuse
- /zig
- /centos
- /php
- /scala
- /react-native
- /lisp
- /sublime-text
- /textmate
- /nixos
- /debian
- /agda
- /deno
- /django
- /kubuntu
- /arch-linux
- /nodejs
- /spring
- /ubuntu
- /revery
- /manjaro
- /diversity
- /lua
- /julia
- /markdown
- /laravel









