Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/divs1210/xodarap
Fearless recursion in Clojure!
https://github.com/divs1210/xodarap
clojure library recursion
Last synced: about 5 hours ago
JSON representation
Fearless recursion in Clojure!
- Host: GitHub
- URL: https://github.com/divs1210/xodarap
- Owner: divs1210
- License: epl-1.0
- Created: 2018-10-09T23:02:17.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2020-05-20T10:08:02.000Z (over 4 years ago)
- Last Synced: 2024-09-15T21:00:22.879Z (2 months ago)
- Topics: clojure, library, recursion
- Language: Clojure
- Size: 22.5 KB
- Stars: 74
- Watchers: 6
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# xodarap
[![Clojars Project](https://img.shields.io/clojars/v/xodarap.svg)](https://clojars.org/xodarap)
[![Build Status](https://travis-ci.com/divs1210/xodarap.svg?branch=master)](https://travis-ci.com/divs1210/xodarap)Fearless recursion in Clojure!
## Why
The JVM puts a hard limit on how deep our functions can recurse without blowing the stack:
```clojure
;; Ex. 1
;; =====
(defn unsafe-factorial [n]
(if (< n 2)
1
(*' n (unsafe-factorial (dec n)))))(unsafe-factorial 5000)
;; => StackOverflowError clojure.lang.Numbers.lt (Numbers.java:3816)
```This is a very simple example, and the fn can be defined in a tail-recursive manner to avoid this issue,
but we are not always this lucky! Consider:```clojure
;; Ex. 2
;; =====
(defn unsafe-ackermann [m n]
(cond (zero? m) (inc n)
(zero? n) (recur (dec m) 1)
:else (recur (dec m) (unsafe-ackermann m (dec n)))))(unsafe-ackermann 3 10)
;; => StackOverflowError clojure.lang.Numbers$LongOps.inc (Numbers.java:545)
```
Try converting the above to use tail recursion - possible, but not trivial.... and then there are mutually-recursive functions:
```clojure
;; Ex. 3
;; =====
(letfn [(is-odd? [n]
(if (zero? n)
false
(is-even? (dec n))))
(is-even? [n]
(if (zero? n)
true
(is-odd? (dec n))))]
(is-even? 10000))
;; => StackOverflowError clojure.lang.Numbers$LongOps.isZero (Numbers.java:443)
```
Clojure's `recur` form doesn't help us here, so we generally end up using a [trampoline](https://clojuredocs.org/clojure.core/trampoline).Is there a general way to recurse safely (ie without blowing up the stack)
in all these cases, and without changing the structure of our code?## Usage
```clojure
(use 'xodarap.core);; Ex. 1
;; =====
(defrec factorial [n]
(if (< n 2)
1
(*' n (rec (factorial (dec n))))))(factorial 5000)
;; => 4228577926605543522201064200233584405390...
```Here, we define a safe recursive factorial fn using the `defrec` form. Note that the
recursive call to itself is wrapped in a `rec` form.```clojure
;; Ex. 2
;; =====
(defrec ackermann [m n]
(cond (zero? m) (inc n)
(zero? n) (recur (dec m) 1)
:else (recur (dec m) (rec (ackermann m (dec n))))))(ackermann 3 10)
;; => 8189 (after a long pause)
```We can crack tougher nuts using the same method.
```clojure
;; Ex. 3
;; =====
(letrec [(is-odd? [n]
(if (zero? n)
false
(rec (is-even? (dec n)))))
(is-even? [n]
(if (zero? n)
true
(rec (is-odd? (dec n)))))]
(is-even? 10000))
;; => true
```Here we define mutually recursive functions using `letrec`.
```clojure
;; like fn
(recfn fact [n]
(if (< n 2)
1
(*' n (rec (fact (dec n))))))
```Similarly, `recfn` is an alternative to `fn`.
## License
Copyright © 2018 Divyansh Prakash
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.