views:

332

answers:

3

What happens when I do the following?

(define ((func x) y)
    (if (zero? y)
        ((func x) 1)
        12))

I understand that I can do this:

(define curried (func 5))

And now I can use curried. What I'm curious about is in the definition of the function. Does the line

((func x) 1)

create a new lambda with x as the argument, and then invoke it on 1? Or is it smarter than that and it just re-uses the existing one. (For example, if I do (curried 0), the ((func x) 1) line would be equivalent to (curried 1) - does PLAI Scheme do this?)

+2  A: 

It's been too long since I worked with scheme, but you might find this article helpful. It describes the implementation of two macros, c-lambda and c-define which allow implicit curried definitions of lambda expressions.

plinth
hmm, an interesting article, but this behavior i'm asking about is built-in to plai scheme, and i want to know how it is implemented - the article implements a different version of currying.
Claudiu
+6  A: 

In the Scheme standard it is specified that

(define (f x) 42) is short for (define f (lambda (x) 42)) .

The natural (non-standard) generatlization implies:

(define ((f x) y) (list x y)) is short for (define (f x) (lambda (y) (list x y)))
                which is short for (define f (lambda (x) (lambda (y) (list x y))))

To test it, let's try the example in DrScheme

Welcome to DrScheme, version 4.1.3.3-svn5dec2008 [3m]. Language: Module; memory limit: 384 megabytes.

(define ((f x) y) (list x y)) (f 1)

((f 1) 2) (1 2)

If we name the temporary value, it might be easier to see what happens:

(define h (f 1)) (h 2) (1 2) (h 3) (1 3)

Since "PLAI Scheme" is implemented in DrScheme, I believe it inherits this shortcut notation.

soegaard
gotcha. to answer my question now - this expansion will happen even in the function 'f', right?
Claudiu
Which f are talking about now?
soegaard
A: 

soegaard's answer is correct - this is the traditional expansion. However, drscheme is smart!

The following code I've found to be equivalent in running time:

Original source:

(define ((substitute lv value) e)
  (cond [(LogicVar? e)
  (type-case LogicVar e
    [lv-any (id) (if (symbol=? id (lv-any-id lv))
       value
       e)]
    [lv-cons (f r) 
      (lv-cons ((substitute lv value) f)
        ((substitute lv value) r))])]
 [(cons? e)
  (cons ((substitute lv value) (car e))
        ((substitute lv value) (cdr e)))]
 [else e]))

Attempt at optimization:

(define (substitute lv value)
  (local ([define inner
     (lambda (e)
       (cond [(LogicVar? e)
       (type-case LogicVar e
         [lv-any (id) (if (symbol=? id (lv-any-id lv))
     value
     e)]
         [lv-cons (f r) 
    (lv-cons (inner f)
      (inner r))])]
      [(cons? e)
       (cons (inner (car e))
      (inner (cdr e)))]
      [else e]))])
    inner))

Code which heavily uses this function (multiple times, not just once) runs at 1800 ms for both versions. More interestingly, this version (my visualization of what was happening):

(define (substitute lv value)
  (local ([define inner
     (lambda (e)
       (cond [(LogicVar? e)
       (type-case LogicVar e
         [lv-any (id) (if (symbol=? id (lv-any-id lv))
     value
     e)]
         [lv-cons (f r) 
    (lv-cons ((substitute lv value) f)
      ((substitute lv value) r))])]
      [(cons? e)
       (cons ((substitute lv value) (car e))
      ((substitute lv value) (cdr e)))]
      [else e]))])
    inner))

Runs at 2000 ms. So there is definitely a slow-down if the calls to substitute within substitute were each creating a lambda, but it appears this is not the case with the shortcut notation.

Claudiu
If you benchmark in DrScheme remember to turn debugging of(in the language menu, choose "Details") - or try the timings in MzScheme.
soegaard