I have been trying to write a trace tool for bootstrapped clojurescript — using
an approach similar to clojure's tools.trace and CL's dtrace. It involves
wrapping the traced function and redefining the symbol to point to the wrapped
function. In clojure's tools.trace, the var's root binding is altered.
I have run into an issue with recursive functions. The emitted javascript turns
out to be different based on how the function is defined.
Consider the following definitions for calculating factorial recursively. I am
using the factorial function just for illustration purposes. I am using Mike
Fikes' excellent Planck real (bootstrapped clojurescript on MacOS). Based on
analysis below, I think emitted javascript for definition of f1 should be same
as that of f2. Any thoughts?
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn f1 [n] (if (< n 2) 1 (* n (f1 (dec n)))))
(def f2 (fn f2-lbl [n] (if (< n 2) 1 (* n (f2 (dec n))))))
(def f3 (fn f3-lbl [n] (if (< n 2) 1 (* n (f3-lbl (dec n))))))
(defn f4 [n] (if (< n 2) 1 (* n (cljs.user/f4 (dec n)))))
(def f5 (fn [n] (if (< n 2) 1 (* n (f5 (dec n))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
If I wrap the factorial function and redefine the name symbol in with-redefs,
only f2 and f4 work as expected. No such issues with clojure.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
cljs.user=>(defn wrapper [f] (fn [x] (println "wrapped call arg-> " x) (f x)))
#'cljs.user/wrapper
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1)
cljs.user=>with-redefs [f1 (wrapper f1)] (f1 5))
wrapped call arg-> 5
120
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2)
cljs.user=> (with-redefs [f2 (wrapper f2)] (f2 5))
wrapped call arg-> 5
wrapped call arg-> 4
wrapped call arg-> 3
wrapped call arg-> 2
wrapped call arg-> 1
120
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3)
cljs.user=>(with-redefs [f3 (wrapper f3)] (f3 5))
wrapped call arg-> 5
120
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4)
cljs.user=>(with-redefs [f4 (wrapper f4)] (f4 5))
wrapped call arg-> 5
wrapped call arg-> 4
wrapped call arg-> 3
wrapped call arg-> 2
wrapped call arg-> 1
120
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
5)
cljs.user=>(with-redefs [f5 (wrapper f5)] (f5 5))
wrapped call arg-> 5
120
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Turning verbose option on with planck, it is easy to see what is happening.
The emitted javascript is different depending on how we are defining the
clojurescript recursive function
1) cljs.user=> (defn f1 [n] (if (< n 2) 1 (* n (f1 (dec n)))))
Evaluating Expression
cljs.user.f1 = (function cljs$user$f1(n){
if((n < (2))){
return (1);
} else {
return (n * cljs$user$f1.call(null,(n - (1))));
}
})
#'cljs.user/f1
---- Look at the return statement. The recursive ignition calls cljs$user$f1.
This means if I redefine cljs.user.f1 , the behavior of the function won't
change at all.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2) cljs.user=> (def f2 (fn f2-lbl [n] (if (< n 2) 1 (* n (f2 (dec n))))))
Evaluating Expression
cljs.user.f2 = (function cljs$user$f2_lbl(n){
if((n < (2))){
return (1);
} else {
return (n * cljs.user.f2.call(null,(n - (1))));
}
})
#'cljs.user/f2
--- Here, the recursive call in return statement is to cljs.user.f2 rather than
cljs$user$f2_lbl.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3) cljs.user=> (def f3 (fn f3-lbl [n] (if (< n 2) 1 (* n (f3-lbl (dec n))))))
Evaluating Expression
cljs.user.f3 = (function cljs$user$f3_lbl(n){
if((n < (2))){
return (1);
} else {
return (n * cljs$user$f3_lbl.call(null,(n - (1))));
}
})
#'cljs.user/f3
-- As expected, the recursive call is to cljs$user$f3_lbl and not cljs.user.f3
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
4) cljs.user=> (defn f4 [n] (if (< n 2) 1 (* n (cljs.user/f4 (dec n)))))
Evaluating Expression
cljs.user.f4 = (function cljs$user$f4(n){
if((n < (2))){
return (1);
} else {
return (n * cljs.user.f4.call(null,(n - (1))));
}
})
#'cljs.user/f4
--- cljs.user.f4 is used for the recursive call
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
5) (def f5 (fn [n] (if (< n 2) 1 (* n (f5 (dec n))))))
Evaluating Expression
cljs.user.f5 = (function cljs$user$f5(n){
if((n < (2))){
return (1);
} else {
return (n * cljs$user$f5.call(null,(n - (1))));
}
})
#'cljs.user/f5
-- Quite similar to f1.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Again, I think the definition for f1 should emit javascript similar to
definition for f2. Any thoughts?
--
Note that posts from new members are moderated - please be patient with your
first post.
---
You received this message because you are subscribed to the Google Groups
"ClojureScript" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/clojurescript.