I am reading the fix-point
of SICP:
#+begin_src emacs-lisp :session sicp :lexical t
(defvar tolerance 0.00001)
(defun fixed-point(f first-guess)
(defun close-enoughp(v1 v2)
(< (abs (- v1 v2)) tolerance))
(defun try(guess) ;;
(let ((next (funcall f guess)))
(if (close-enoughp guess next)
next
(try next))))
(try first-guess))
(fixed-point #'cos 1.0)
#+end_src
#+RESULTS:
: 0.7390822985224024
From the above case, I learned that one nature of while
is the abstract concept "try"
#+begin_src ipython :session sicp :results output pySrc/sicp_fixedpoint2.py
import math
def fixed_point(f, guess):
while True:
nex = f(guess)
if abs(guess-nex) < 0.0001:
return nex
else:
guess = nex #local assignment is nature of lambda
print(fixed_point(math.cos, 1))
#+end_src
#+RESULTS:
: 0.7390547907469174
So I could write iteration in python just with the effective functional abstraction thinking.
When reflect on try
, more than "try is a while in iteration", what it teach me?
It could be reframed without try
, but return return fixed_point(f, nex)
directly.
#+begin_src ipython :session sicp :results output :tangle pySrc/sicp_fixedpoint.py
import math
tolerance = 0.00001
def fixed_point(f, guess):
def good_enoughp(a, b):
return abs(a-b) < tolerance
nex = f(guess)
if good_enoughp(guess, nex):
return nex
else:
return fixed_point(f, nex)
print(fixed_point(math.cos, 1))
#+end_src
#+RESULTS:
: 0.7390822985224024
So why SICP introduced try
here, I guess efficiency might not be the author's key consideration.
Test with elisp
#+begin_src emacs-lisp :session sicp :lexical t
(defvar tolerance 0.00001)
(defun fixed-point(f guess)
(defun close-enoughp(v1 v2) ;
(< (abs (- v1 v2)) tolerance))
(let ((next (funcall f guess)))
(if (close-enoughp guess next)
next
(fixed-point f next)))
)
;;(trace-function #'fixed-point)
(fixed-point #'cos 1.0)
#+end_src
#+RESULTS:
: 0.7390822985224024
It works as expected.
It seems that return fixed-point f next
is a bit cleaner than a inner iteration with try.
What's the consideration of SICP here, what was intended to teach?
It's the opposite: it's cleaner and more efficient with try
because it doesn't need to redefine the good-enough-p
.
(also, you're not supposed to use recursion in Python).
The version with try
is better than the version which calls the top function, fixed-point
, because fixed-point
contains inner definitions, of the functions good-enough-p
and try
. A simple-minded compiler would compile it so that on each call it actually makes those definitions anew, again and again, on each call. With try
there's no such concern as it is already inside the fixed-point
's inner environment where good-enough-p
is already defined, and so try
can just run.
(correction/clarification: the above treats your code as if it were Scheme, with internal define
s instead of the Common Lisp with defun
s as you show. SICP is Scheme, after all. In Common Lisp / ELisp there's not even a question -- the internal defun
s will always be performed, on each call to the enclosing function, just (re)defining the same functions at the top level over and over again.)
Incidentally, I like your Python loop translation, it is a verbatim translation of the Scheme's tail-recursive loop, one to one.
Your while
translation is exactly what a Scheme compiler is supposed to be doing given the first tail-recursive Scheme code in your question. The two are exactly the same, down to the "horrible while True ...
with an escape" which, personally, I quite like for its immediacy and clarity. Meaning, I don't need to keep track of which value gets assigned to what variable and which variable gets returned in the end -- instead, a value is just returned, just like it is in Scheme.