trampoline can be used to convert algorithms requiring mutual recursion without stack consumption. Calls f with supplied args, if any. If f returns a fn, calls that fn with no arguments, and continues to repeat, until the return value is not a fn, then returns that non-fn value. Note that if you want to return a fn as a final value, you must wrap it in some data structure and unpack it after trampoline returns.
(defn foo [x] (if (< x 0) (println "done") #(foo (do (println :x x) (dec x))))) ;; #'user/foo ;; `trampoline` will keep calling the function ;; for as long as "foo" returns a function. (trampoline foo 10) ;; :x 10 ;; :x 9 ;; :x 8 ;; :x 7 ;; :x 6 ;; :x 5 ;; :x 4 ;; :x 3 ;; :x 2 ;; :x 1 ;; :x 0 ;; done ;;=> nil
;; Short tutorial-style article with example of using trampoline at this link: ;; http://jakemccrary.com/blog/2010/12/06/trampolining-through-mutual-recursion/
;; Using mutually recursive functions to implement a finite state machine (FSM) ;; This machine has three states {a b c} and ;; seven transitions {:a-b :a-c :b-a :b-c :c-a :c-b :final}. (defn foo [cmds] (letfn [(a-> [[_ & rs]] #(case _ :a-b (b-> rs) :a-c (c-> rs) false)) (b-> [[_ & rs]] #(case _ :b-a (a-> rs) :b-c (c-> rs) false)) (c-> [[_ & rs]] #(case _ :c-a (a-> rs) :c-b (c-> rs) :final true false))] (trampoline a-> cmds))) (foo [:a-b :b-c :c-a :a-c :final]) ;;=> true
;; from <the joy of clojure 2nd> ;; Mutually recursive functions are nice for implementing ;; finite state machines (FSAs) (defn elevator [commands] (letfn [(ff-open [[_ & r]] "When the elevator is open on the 1st floor it can either close or be done." #(case _ :close (ff-closed r) :done true false)) (ff-closed [[_ & r]] "When the elevator is closed on the 1st floor it can either open or go up." #(case _ :open (ff-open r) :up (sf-closed r) false)) (sf-closed [[_ & r]] "When the elevator is closed on the 2nd floor it can either go down or open." #(case _ :down (ff-closed r) :open (sf-open r) false)) (sf-open [[_ & r]] "When the elevator is open on the 2nd floor it can either close or be done" #(case _ :close (sf-closed r) :done true false))] (trampoline ff-open commands))) (elevator [:close :open :close :up :open :open :done]) ;=> false (elevator [:close :up :open :close :down :open :done]) ;=> true ;; run at your own risk! (elevator (cycle [:close :open])) ; ... runs forever
;; without trampoline (declare hatt) (defn catt [n] (when-not (zero? n) (when (zero? (rem n 100)) (println "catt:" n)) (hatt (dec n)))) (defn hatt [n] (when-not (zero? n) (if (zero? (rem n 100)) (println "hatt:" n)) (catt (dec n)))) ;; std=> catt: 100000000 ;; std=> catt: 99999900 ;; std=> catt: 99999800 ;; std=> catt: 99999700 ;; std=> catt: 99999600 ;; std=> catt: 99999500 ;; std=> catt: 99999400 ;; . ;; . ;; . ;; std => CompilerException java.lang.StackOverflowError ;; with trampoline (declare hattr) (defn cattr [n] (when-not (zero? n) (when (zero? (rem n 100)) (println "cattr:" n)) (fn [] (hattr (dec n))))) (defn hattr [n] (when-not (zero? n) (if (zero? (rem n 100)) (println "hatttr:" n)) (fn [] (cattr (dec n))))) (trampoline cattr 10000000) ;; std=> catt: 100000000 ;; std=> catt: 99999900 ;; std=> catt: 99999800 ;; std=> catt: 99999700 ;; std=> catt: 99999600 ;; std=> catt: 99999500 ;; std=> catt: 99999400 ;; . ;; . ;; . ;; std=> catt: 100
Evaluates the exprs in a lexical context in which the symbols in the binding-forms are bound to th...
Evaluates the exprs in order, then, in parallel, rebinds the bindings of the recursion point to the ...
fnspec ==> (fname [params*] exprs) or (fname ([params*] exprs)+) Takes a vector of function specs...
A tutorial on how to use trampoline is available here:
http://pramode.net/clojure/2010/05/08/clojure-trampoline/
and here:
http://jakemccrary.com/blog/2010/12/06/trampolining-through-mutual-recursion.html
The links above are dead. Here are the archived versions: