This document is a presentation made for a YouTube video for my channel JESPROTECH about the evolution of tailrec through the years accross some of the most well known, and some not so known languages. The goal isn't to go through all languages ever invented, but to gain a prespective about tailrec that exists already in programming languages since the 1950's. The idea is to situate us in time and understand where we come from when we talk about tailrec and tail call optimization (TCO).
2. Who am I?
Overview
Understanding the problems
Project objective
Target audience
Market trends
Cycle diagram
Introducing: Lorem ipsum
Spotlight on desktop
Spotlight on mobile
Spotlight on landscape view on mobile
Spotlight on wearables
Spotlight on tablet
Spotlight on landscape view on tablet
Spotlight on wearables
João Esperancinha
● Java
● Kotlin
● Groovy
● Scala
● Software Engineer 10+ years
● JESPROTECH owner for 1 year
● Kong Champion/Java Professional/Spring Professional
Project timeline
4. What is tailrec?
● Recursivity
No, not just any
recursivity
Any Recursivity?
● Tail recursivity
● The last function call!
And then?
5. What defines a tail recursive function?
A recursive function is said to be tail recursive
when:
● The last function call is the call to the
recursive function
● The last function call doesn’t occur in
combination with any calculated value or
any other function call
● In other words nothing should be stored per
stack frame
Awesome!
6. When did Tailrec become a thing?
01 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eleifend a diam
quis suscipit. Class aptent taciti sociosqu ad litora et nec torquent per conubia
nostra.
02 Amet, consectetur adipiscing elit. Curabitur eleifend a diam quis suscipit. Class
aptent taciti The ad litora torquent per conubia nostra.fd
03 Consectetur adipiscing elit. Curabitur eleifend lorem a diam quis suscipit. Class
aptent taciti sociosqu ad litora torquent ipsum per conubia nostra.
Late 1950’s computing studies showed that tail
recursive algorithms could be easily reimplemented
with iterative alternatives, making them more
efficient
No more usage of Stack Frames
No need to hold values in memory for every iteration
Tail Recursivity
Became a thing!
Space complexity reduced to O(1)
8. The 1950’s
1957 - IBM Mathematical Formula Translating System, or
Fortran. Created by John Backus team
1959 - COBOL by CODASYL
(Conference/Committee on Data Systems Languages)
1958 - LISP by John McCarthy
By Jooja
CC BY-SA 4.0 DEED
By PIerre.Lescanne
CC BY-SA 4.0 DEED
John Backus
By WikiPedant
CC BY-SA 2.0 DEED
John McCarthy
By Jonathan Schilling
CC BY-SA 4.0 DEED
COBOL punch cards
9. The 1970’s to the 90’s
● 1983-84 - Standard ML(Meta Language) by Robin Milner,
Mads Tofte, and others at the University of Edinburgh
● 1975 - Scheme by Gerald Jay Sussman and Guy L. Steele Jr
● 1990 - Haskell named after Haskell
Curry and released by committee of
researchers and academics
● 1986 - Erlang by Joe Armstrong,
Robert Virding, and Mike Williams at
Ericsson
By Magnus Manske
CC BY-SA 1.0 DEED
Gerald Jay Sussman
By George Ruban
CC BY-SA 4.0 DEED
Guy L. Steele Jr
By Flavbeli
CC BY 2.0 DEED
University of Edinburgh
10. The 1990’s to 2016 - JVM Evolution
● 2007 Clojure by Rich Hickey
● 1995 - Java by Sun Microsystems,
however the invention of Java is attributed to James Gosling
and colleagues Mike Sheridan, and Patrick Naughton
● 2016 by the team led by Andrey
Breslav
● 2003 Scala by Martin Odersky
James Gosling
By Peter Campbell
CC BY-SA 4.0 DEED
Rich Hickey
By Tapestry Dude - Flickr
CC BY-SA 2.0 DEED
Martin Odersky
By LindaPoengPhotography
CC BY 3.0 DEED
By Lightbend, Inc.
CC BY 4.0 DEED
11. What was the whole point?
● Function Simplification
● Use recursivity without impacting performance
● Make code easy to read
● Avoid the usage of global variables
● Adhere to immutability principles
Much later on…
13. A pseudo-code example
function fibonacci(n) = {
var result = 0
var next = 1
var i = 0
while (i < n) {
val temp = next
next = result + next
result = temp
i += 1
}
return result
}
Leonardo of Pisa
By Hans-Peter Postel
CC BY 2.5 DEED
Statue of Fibonacci (1863) by
Giovanni Paganucci in the
Camposanto di Pisa
Fibonacci Sequence
0, 1, 1, 2, 3, 5, 8, 13, 21, 34 … etc
F(n) = F(n-1) + F(n-2)
Iterations
● Efficient 👍
● Difficult to Read 👎
O(n) time / O(1) space
By Romain
CC BY-SA 4.0 DEED
Graphical
representation
14. A pseudo-code example
function fibonacci(n) {
if (n <= 1) {
return n
} else {
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
Fibonacci Sequence
0, 1, 1, 2, 3, 5, 8, 13, 21, 34 … etc
F(n) = F(n-1) + F(n-2)
Recursivity
● Not very efficient 👎
● Easier to read 👍
O(2^n) time / O(2^n) space
function fibonacci(n) {
if (n <= 1) {
return n
} else {
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
function fibonacci(n) {
if (n <= 1) {
return n
} else {
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
function fibonacci(n) {
if (n <= 1) {
return n
} else {
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
function fibonacci(n) {
if (n <= 1) {
return n
} else {
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
function fibonacci(n) {
if (n <= 1) {
return n
} else {
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
function fibonacci(n) {
if (n <= 1) {
return n
} else {
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
(…) (…) (…) (…)
15. A pseudo-code example
Fibonacci Sequence
0, 1, 1, 2, 3, 5, 8, 13, 21, 34 … etc
F(n) = F(n-1) + F(n-2)
Tail Recursivity
● Back to being efficient 👍
● Still easy to read, but less 👎
O(n) time / O(1) space
function fibonacci(n) {
function fiboHelper(n, result, next) {
if (n == 0) result
else return fiboHelper(n - 1, next, result + next)
}
return fiboHelper(n, 0, 1)
}
● Efficiency isn’t 100% guaranteed 👎
function fibonacci(n) {
function fiboHelper(n, result, next) {
if (n == 0) result
else return fiboHelper(n - 1, next, result + next)
}
return fiboHelper(n, 0, 1)
}
function fibonacci(n) {
function fiboHelper(n, result, next) {
if (n == 0) result
else return fiboHelper(n - 1, next, result + next)
}
return fiboHelper(n, 0, 1)
}
function fibonacci(n) {
function fiboHelper(n, result, next) {
if (n == 0) result
else return fiboHelper(n - 1, next, result + next)
}
return fiboHelper(n, 0, 1)
}
function fibonacci(n) {
function fiboHelper(n, result, next) {
if (n == 0) result
else return fiboHelper(n - 1, next, result + next)
}
return fiboHelper(n, 0, 1)
}
function fibonacci(n) {
function fiboHelper(n, result, next) {
if (n == 0) result
else return fiboHelper(n - 1, next, result + next)
}
return fiboHelper(n, 0, 1)
}
function fibonacci(n) {
function fiboHelper(n, result, next) {
if (n == 0) result
else return fiboHelper(n - 1, next, result + next)
}
return fiboHelper(n, 0, 1)
}
(…)
16. Applying TCO
Tail Call Optimization
function fibonacci(n) {
function fiboHelper(n, result, next) {
if (n == 0) next
else return fiboHelper(n - 1, next, result + next)
}
return fiboHelper(n, 0, 1)
}
function fibonacci(n) {
function fiboHelper(n) = {
var result = 0
var next = 1
var i = 0
while (i < n) {
val temp = next
next = result + next
result = temp
i += 1
}
return result
}
}
How we implement…
… how we would have implemented in the result
18. The Beginnings with Fortran - 1957
● Fortran I: 1957
● Fortran II: 1958
● Fortran III: Not released
● Fortran IV: 1962
● Fortran 66: 1966
● Fortran 77: 1977
● Fortran 90: 1991
● Fortran 95: 1997
● Fortran 2003: 2004
● Fortran 2008: 2010
● Fortran 2018: 2018
PROGRAM FIBONACCI
INTEGER N, I
REAL F1, F2, FIB
N = 100
F1 = 0
F2 = 1
DO I = 2, N
FIB = F1 + F2
F1 = F2
F2 = FIB
END DO
WRITE(*,*) FIB
END
PROGRAM RECURSIVE_FIBONACCI_REAL
INTEGER N
N = 100
DO I = 0, N - 1
CALL fibonacci_real(I, Result)
END DO
WRITE(*, *) Result
CONTAINS
RECURSIVE SUBROUTINE fibonacci_real(N, Result)
INTEGER N
REAL Result
IF (N <= 1) THEN
Result = REAL(N)
ELSE
CALL fibonacci_real(N - 1, Result1)
CALL fibonacci_real(N - 2, Result2)
Result = Result1 + Result2
END IF
END SUBROUTINE fibonacci_real
END
Recursivity
19. Fortran nowadays (since 1991)
program Fibonacci
implicit none
integer :: n, i
real :: fib_prev, fib_current, result
n = 100
fib_prev = 0
fib_current = 1
do i = 1, n
if (i <= 1) then
result = i
else
result = fib_prev + fib_current
fib_prev = fib_current
fib_current = result
end if
end do
print *, "The result is", result
end program Fibonacci
program FibonacciTailRecursive
implicit none
integer :: n, i
real :: result
n = 100
result = fibonacci(n)
print *, "The result is", result
contains
recursive function fibonacci(n) result(fib)
integer, intent(in) :: n
integer :: fib
if (n == 0) then
fib = 0
else if (n == 1) then
fib = 1
else
fib = fibonacci(n - 1) + fibonacci(n - 2)
end if
end function fibonacci
end program FibonacciTailRecursive
program FibonacciTailRecursive
implicit none
integer :: n
real :: result
n = 100
result = fibonacci(n)
print *, "The result is", result
contains
recursive function fibonacci(n) result(fib)
integer, intent(in) :: n
real :: fib
if (n <= 1) then
fib = n
else
fib = fibonacci_helper(n, 0.0, 1.0)
end if
end function fibonacci
recursive function fibonacci_helper(n, a, b) result(fib)
integer, intent(in) :: n
real :: fib, a, b
if (n == 0) then
fib = a
else if (n == 1) then
fib = b
else
fib = fibonacci_helper(n - 1, b, a + b)
end if
end function fibonacci_helper
end program FibonacciTailRecursive
21. LISP 1958
(defun fibonacci-iterative (n)
(if (<= n 1)
n
(fib-iter 0 1 n)))
(defun fib-iter (a b count)
(loop repeat (- count 1)
do (setf (values a b) (values b (+ a b)))
finally (return b)))
(defun fibonacci-recursive (n)
(if (<= n 1)
n
(+ (fibonacci-recursive (- n 1))
(fibonacci-recursive (- n 2)))))
● Lisp: 1958
● Lisp 1.5: 1962
● MacLisp: Late 1960s
● InterLisp: 1966
● Common Lisp: 1984
(defun fibonacci-tail-recursive (n)
(if (<= n 1)
n
(fib-tail-recursive 0 1 n)))
(defun fib-tail-recursive (a b count)
(if (= count 0)
a
(fib-tail-recursive b (+ a b) (- count 1))))
25. Scheme - 1975
(define (fibonacci n)
(fibonacci-iter n 0 1))
(define (fibonacci-iter n a b)
(do ((i n (- i 1))
(x a b)
(y b (+ a b)))
((= i 0) x)
(set! a x)
(set! b y)))
(define (fibonacci n)
(if (or (= n 0) (= n 1))
n
(+ (fibonacci (- n 1)) (fibonacci (- n 2)))))
(display (fibonacci 100))
● Scheme 75: 1975
● Scheme 84: 1984
● Several follow up
releases until 2013
(define (fibonacci n)
(define (fib-helper n a b)
(if (= n 0)
a
(fib-helper (- n 1) b (+ a b))))
(fib-helper n 0 1))
(display (fibonacci 100))
27. Standard Meta Language - 1983/84
● Moby: Moby - 1980’s
● SML/NJ - 1983/1984
● MLton - 1999
● SML '90 - 1990
● SML '97 - 1997
● SML 2007 - 2007
● SML 2022 - 2022
fun fibonacci n =
if n < 2 then n
else fibonacci (n - 1) + fibonacci (n - 2);
structure IntInf = IntInf
fun fibonacci n =
let
fun fibIter n a b =
if n = 0 then
a
else
fibIter (n - 1) b (IntInf.+(a, b))
in
fibIter n 0 1
end;
31. Haskell - 1990
● First draft - 1987
● Haskell 1.0 - 1990
● Haskell 1.1 - 1991
● Haskell 1.2 - 1992
● Haskell 1.3 - 1996
● Haskell 1.4 - 1997
● Haskell 98 - 1998
● Haskell 2010 - 2010
● Haskell 2022 - 2022
fibonacci :: Integer -> Integer
fibonacci 0 = 0
fibonacci 1 = 1
fibonacci n = fibonacci (n - 1) + fibonacci (n -
2)
main :: IO ()
main = do
let result = fibonacci 1000
putStrLn $ "Fibonacci of the order of 1000:
" ++ show result
fibonacciTailRec :: Integer -> Integer
fibonacciTailRec n = fibonacciHelper n 0 1
where
fibonacciHelper 0 a _ = a
fibonacciHelper 1 _ b = b
fibonacciHelper n a b = fibonacciHelper
(n - 1) b (a + b)
main :: IO ()
main = do
let result = fibonacciTailRec 1000
putStrLn $ "Fibonacci of the order of
1000: " ++ show result
33. About Java - 1995
Java deliberately doesn’t support Tail Call Optimization because:
● Backward compatibility
● It might not go well with the JVM architecture because of overhead and potential side-effects.
● Performance and complexity
● The iterative alternative is always possible
● Not very clear if TCO is the best idea for Java
But this didn’t stop JVM alternative
languages to explore TCO possibilities...
39. Clojure - 2007
(defn fibonacci-iterative [n]
(if (<= n 1)
n
(loop [a 0
b 1
i 1]
(if (= i n)
(+ a b)
(recur b (+ a b) (inc i))))))
(defn fibonacci-recursive [n]
(let [n (if (instance? String n)
(Double/parseDouble n)
n)]
(if (<= n 1.0)
n
(+ (fibonacci-recursive (- n 1.0)) (fibonacci-recursive (- n 2.0))))))
(defn fibonacci-tail-rec [n]
(let [n (if (instance? String n)
(Double/parseDouble n)
n)]
(letfn [(fib-tail [n a b]
(if (zero? n)
a
(fib-tail (dec n) b (+ a b))))]
(fib-tail n 0.0 1.0))))
● First version - 2007
● Clojure 1.0 - 2009
● Clojure until 1.11.1 in
2022
40. Clojure - 2007
● First version - 2007
● Clojure 1.0 - 2009
● Clojure until 1.11.1 in
2022
(defn fibonacci-tail-rec-tco [n]
(let [n (if (instance? String n)
(Double/parseDouble n)
n)]
(letfn [(fib [n a b]
(if (zero? n)
a
(recur (dec n) b (+ a b))))]
(fib n 0.0 1.0))))
46. Conclusions
● Which direction is this all taking?
● Is programming still a challenge?
● Why do we do software development?
● Are we being forced into thinking recursively?
● Is Mutability something to be completely removed from a programming language?
● Productivity vs The Joy of Coding
● What happens when we keep thinking only recursively when implementing algorithms?
● Can we blindly rely on the TCO algorithms in the way that we already trust Garbage Collection?
● Do we trust Garbage Collection?
● Can we manually implement a better algorithm with better performance than TCO?
● Would we still be able to do it, if we don’t practice?
● Can we still produce without challenges in the long run?