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
Hi @venkats,
It has been mentioned in the description of ‘Supervisory Job’ title that 2 things as mentioned below result in the same eff...
New
Hello! Thanks for the great book.
I was attempting the Trie (chap 17) exercises and for number 4 the solution provided for the autocorre...
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 running Android Studio “Arctic Fox” 2020.3.1 Patch 2, and I’m embarrassed to admit that I only made it to page 8 before running into ...
New
Hi, I have just acquired Michael Fazio’s “Kotlin and Android Development” to learn about game programming for Android. I have a game in p...
New
Is there any place where we can discuss the solutions to some of the exercises? I can figure most of them out, but am having trouble with...
New
The markup used to display the uploaded image results in a Phoenix.LiveView.HTMLTokenizer.ParseError error.
lib/pento_web/live/product_l...
New
Title: Agile Web Development with Rails 7: (page 70)
I am running windows 11 pro with rails 7.0.3 and ruby 3.1.2p20 (2022-04-12 revision...
New
I got this error when executing the plot files on macOS Ventura 13.0.1 with Python 3.10.8 and matplotlib 3.6.1:
programming_ML/code/03_...
New
I just bought this book to learn about Android development, and I’m already running into a major issue in Ch. 1, p. 20: “Update activity...
New
Other popular topics
Reading something? Working on something? Planning something? Changing jobs even!?
If you’re up for sharing, please let us know what you’...
New
Take your Go skills to the next level by learning how to design, develop, and deploy a distributed service. Start from the bare essential...
New
My first contact with Erlang was about 2 years ago when I used RabbitMQ, which is written in Erlang, for my job. This made me curious and...
New
Hello everyone! This thread is to tell you about what authors from The Pragmatic Bookshelf are writing on Medium.
New
A few weeks ago I started using Warp a terminal written in rust. Though in it’s current state of development there are a few caveats (tab...
New
Author Spotlight
Jamis Buck
@jamis
This month, we have the pleasure of spotlighting author Jamis Buck, who has written Mazes for Prog...
New
Author Spotlight
Mike Riley
@mriley
This month, we turn the spotlight on Mike Riley, author of Portable Python Projects. Mike’s book ...
New
I have always used antique keyboards like Cherry MX 1800 or Cherry MX 8100 and almost always have modified the switches in some way, like...
New
I’m able to do the “artistic” part of game-development; character designing/modeling, music, environment modeling, etc.
However, I don’t...
New
Hello,
I’m a beginner in Android development and I’m facing an issue with my project setup. In my build.gradle.kts file, I have the foll...
New
Categories:
Sub Categories:
Popular Portals
- /elixir
- /rust
- /wasm
- /ruby
- /erlang
- /phoenix
- /keyboards
- /python
- /js
- /rails
- /security
- /go
- /swift
- /vim
- /clojure
- /java
- /emacs
- /haskell
- /svelte
- /onivim
- /typescript
- /kotlin
- /c-plus-plus
- /crystal
- /tailwind
- /react
- /gleam
- /ocaml
- /elm
- /flutter
- /vscode
- /ash
- /html
- /opensuse
- /zig
- /centos
- /deepseek
- /php
- /scala
- /react-native
- /lisp
- /sublime-text
- /textmate
- /nixos
- /debian
- /agda
- /deno
- /django
- /kubuntu
- /arch-linux
- /nodejs
- /spring
- /ubuntu
- /revery
- /manjaro
- /diversity
- /julia
- /lua
- /markdown
- /v









