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
Some minor things in the paper edition that says “3 2020” on the title page verso, not mentioned in the book’s errata online:
p. 186 But...
New
The following is cross-posted from the original Ray Tracer Challenge forum, from a post by garfieldnate. I’m cross-posting it so that the...
New
Hi Travis! Thank you for the cool book! :slight_smile:
I made a list of issues and thought I could post them chapter by chapter. I’m rev...
New
Hi! I know not the intentions behind this narrative when called, on page XI:
mount() |> handle_event() |> render()
but the correc...
New
The book has the same “Problem space/Solution space” diagram on page 18 as is on page 17. The correct Problem/Solution space diagrams ar...
New
I’m new to Rust and am using this book to learn more as well as to feed my interest in game dev. I’ve just finished the flappy dragon exa...
New
I’m under the impression that when the reader gets to page 136 (“View Data with the Database Inspector”), the code SHOULD be able to buil...
New
I’m not quite sure what’s going on here, but I’m unable to have to containers successfully complete the Readiness/Liveness checks. I’m im...
New
I found an issue in Chapter 7 regarding android:backgroundTint vs app:backgroundTint.
How to replicate:
load chapter-7 from zipfile i...
New
The allprojects block listed on page 245 produces the following error when syncing gradle:
“org.gradle.api.GradleScriptException: A prob...
New
Other popular topics
Write Elixir tests that you can be proud of. Dive into Elixir’s test philosophy and gain mastery over the terminology and concepts that u...
New
I’m thinking of buying a monitor that I can rotate to use as a vertical monitor?
Also, I want to know if someone is using it for program...
New
Design and develop sophisticated 2D games that are as much fun to make as they are to play. From particle effects and pathfinding to soci...
New
Small essay with thoughts on macOS vs. Linux:
I know @Exadra37 is just waiting around the corner to scream at me “I TOLD YOU SO!!!” but I...
New
Hello everyone! This thread is to tell you about what authors from The Pragmatic Bookshelf are writing on Medium.
New
If you get Can't find emacs in your PATH when trying to install Doom Emacs on your Mac you… just… need to install Emacs first! :lol:
bre...
New
If you want a quick and easy way to block any website on your Mac using Little Snitch simply…
File > New Rule:
And select Deny, O...
New
Inside our android webview app, we are trying to paste the copied content from another app eg (notes) using navigator.clipboard.readtext ...
New
Develop, deploy, and debug BEAM applications using BEAMOps: a new paradigm that focuses on scalability, fault tolerance, and owning each ...
New
I’m able to do the “artistic” part of game-development; character designing/modeling, music, environment modeling, etc.
However, I don’t...
New
Categories:
Sub Categories:
Popular Portals
- /elixir
- /rust
- /wasm
- /ruby
- /erlang
- /phoenix
- /keyboards
- /python
- /js
- /rails
- /security
- /go
- /swift
- /vim
- /java
- /clojure
- /emacs
- /haskell
- /typescript
- /svelte
- /onivim
- /kotlin
- /c-plus-plus
- /crystal
- /tailwind
- /react
- /gleam
- /ocaml
- /elm
- /vscode
- /flutter
- /html
- /ash
- /deepseek
- /zig
- /opensuse
- /centos
- /php
- /scala
- /react-native
- /lisp
- /sublime-text
- /textmate
- /nixos
- /debian
- /agda
- /deno
- /django
- /kubuntu
- /nodejs
- /arch-linux
- /spring
- /ubuntu
- /revery
- /manjaro
- /lua
- /diversity
- /julia
- /quarkus
- /laravel









