Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the standard way to write nested define statements (like in scheme) for clojure?

All examples are taken from the SICP Book: http://sicpinclojure.com/?q=sicp/1-3-3-procedures-general-methods

This was motivated from the MIT video series on LISP - http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-001-structure-and-interpretation-of-computer-programs-spring-2005/video-lectures/2a-higher-order-procedures/

In scheme, you can put 'define' inside another 'define':

(define (close-enough? v1 v2)
    (define tolerance 0.00001)
    (< (abs (- v1 v2)) tolerance ) )

In clojure, there is the 'let' statement with the only difference that it is nested:

(defn close-enough? [v1 v2]
    (let [tolerance 0.00001]
         (< (Math/abs (- v1 v2) ) 
            tolerance) ) )

But what about rewriting in clojure something bigger like this?:

(define (sqrt x)
  (define (fixed-point f first-guess)
    (define (close-enough? v1 v2)
      (define tolerance 0.00001)
      (< (abs (- v1 v2)) tolerance))
    (define (try guess)
      (let ((next (f guess)))
         (if (close-enough? guess next)
            next
            (try next))))
    (try first-guess))
  (fixed-point (lambda (y) (average y (/ x y)))
           1.0))

This does in fact work but looks very unconventional...

(defn sqrt [n]
  (let [precision       10e-6
        abs             #(if (< % 0) (- %) %)
        close-enough?   #(-> (- %1 %2) abs (< precision))
        averaged-func   #(/ (+ (/ n %) %) 2)
        fixed-point     (fn [f start]
                            (loop [old start
                                   new (f start)]
                                (if (close-enough? old new) 
                                    new
                                    (recur new (f new) ) ) ) )]

        (fixed-point averaged-func 1) ) )

 (sqrt 10)

UPDATED Mar/8/2012

Thanks for the answer!

Essentially 'letfn' is not too different from 'let' - the functions being called have to be nested in the 'letfn' definition (as opposed to Scheme where the functions are used in the next sexp after its definitions and only existing within the scope of the top-level function in which it is defined).

So another question... Why doesn't clojure give the capability of doing what scheme does? Is it some sort of language design decision? What I like about the scheme organization is:

  • 1) The encapsulation of ideas so that I as the programmer have an idea as to what little blocks are being utilized bigger block - especially if I am only using the little blocks once within the big block (for whatever reason, even if the little blocks are useful in their own right).

  • 2) This also stops polluting the namespace with little procedures that are not useful to the end user (I've written clojure programs, came back to them a week later and had to re-learn my code because it was in a flat structure and I felt that I was looking at the code inside out as opposed to in a top down manner).

  • 3) A common method definition interface so I can pull out a particular sub-method, de-indent it test it, and paste the changed version back without too much fiddling around.

Why isn't this implemented in clojure?

like image 742
zcaudate Avatar asked Mar 27 '12 13:03

zcaudate


2 Answers

the standard way to write nested, named, procedures in clojure is to use letfn.

as an aside, your example use of nested functions is pretty suspicious. all of the functions in the example could be top-level non-local functions since they're more or less useful on their own and don't close over anything but each other.

like image 169
Joost Diepenmaat Avatar answered Oct 12 '22 13:10

Joost Diepenmaat


letfn is the standard way.

But since Clojure is a Lisp, you can create (almost) any semantics you want. Here's a proof of concept that defines define in terms of letfn.

(defmacro define [& form]
  (letfn [(define? [exp]
            (and (list? exp) (= (first exp) 'define)))
          (transform-define [[_ name args & exps]]
            `(~name ~args
               (letfn [~@(map transform-define (filter define? exps))]
                 ~@(filter #(not (define? %)) exps))))]
    `(defn ~@(transform-define `(define ~@form)))))

(define sqrt [x]
  (define average [a b] (/ (+ a b) 2))
  (define fixed-point [f first-guess]
    (define close-enough? [v1 v2]
      (let [tolerance 0.00001]
        (< (Math/abs (- v1 v2)) tolerance)))
    (define tryy [guess]
      (let [next (f guess)]
        (if (close-enough? guess next)
          next
          (tryy next))))
    (tryy first-guess))
  (fixed-point (fn [y] (average y (/ x y)))
               1.0))

(sqrt 10)   ; => 3.162277660168379

For real code, you'd want to change define to behave more like R5RS: allow non-fn values, be available in defn, defmacro, let, letfn, and fn, and verify that the inner definitions are at the beginning of the enclosing body.

Note: I had to rename try to tryy. Apparently try is a special non-function, non-macro construct for which redefinition silently fails.

like image 27
Peter Winton Avatar answered Oct 12 '22 14:10

Peter Winton