[comp.lang.scheme] Scheme Digest #8, Efficiency of Y

kranz@WHEATIES.AI.MIT.EDU (David Kranz) (11/15/88)

Edward Wang is correct that the time in the example is dominated by bignum
arithmetic.  I changed the * in factorial to + and got the following result in
T3.1:

(factorial-loop 20000) -> .1 sec

(factorial-rec 20000) -> .22 sec

(factorial-lfp 20000) -> 1.32 sec

With *:

(factorial-rec 100) -> .42 sec

(factorial-rec 1000) -> 57.78 sec

cph@KLEPH.AI.MIT.EDU (Chris Hanson) (11/16/88)

   Date: Tue, 15 Nov 88 10:26:46 EST
   From: kranz@wheaties.ai.mit.edu (David Kranz)

   Edward Wang is correct that the time in the example is dominated by
   bignum arithmetic.  I changed the * in factorial to + and got the
   following result in T3.1:

I tried a similar experiment in MIT Scheme (using + instead of *,
except a smaller loop to account for smaller fixnums), with the
following results:

    (factorial-loop 100) -> 1.03 msec

    (factorial-rec 100) -> 1.0 msec

    (factorial-lfp 100) -> 2.74 msec

Bill Rozas has expended no small effort in the MIT Scheme compiler to
make the Y combinator produce good results, and these timings are
evidence of that.  Still not perfect, but I believe Bill claims that
he can make the output code identical given a bit more work.

max@polya.Stanford.EDU (Max Hailperin) (11/17/88)

In article <8811161012.AA13091@kleph.ai.mit.edu> cph@zurich.ai.mit.edu writes:
>Bill Rozas has expended no small effort in the MIT Scheme compiler to
>make the Y combinator produce good results, and these timings are
>evidence of that.  Still not perfect, but I believe Bill claims that
>he can make the output code identical given a bit more work.

As this remark points out, there is no reason from an efficiency
standpoint to not simply define letrec in terms of Y.  While Y may be
expensive in some general cases, as long as it only appears
surrounding an explicit lambda, there is no particular reason a
compiler can't catch on to what's going on. 

HOWEVER,

There is another reason, in Scheme, not to use Y: it breaks the fact
that you can use procedures as objects.  While the R^3R says that a
procedure created by a given lambda at a given time will be eqv to
itself (and implies eq as well, though on looking I see that isn't in
there -- is this a mistake?), the same does not necessarily hold for
the various incarnations of a procedure that Y will churn out (or
rather, that Y is entitled to churn out: presumably in Rozas's
implementation there is indeed only one procedure).

Perhaps I'm wrong to mix together such disparate worlds as Y and
eqvness of procedures belong to, but I do consider this to be
something of an issue.  Does anyone else?

Krulwich-Bruce@cs.yale.edu (Bruce Krulwich) (11/17/88)

In article <8811161012.AA13091@kleph.ai.mit.edu>, cph@KLEPH (Chris Hanson)
writes:
>Bill Rozas has expended no small effort in the MIT Scheme compiler to
>make the Y combinator produce good results, and these timings are
>evidence of that.  Still not perfect, but I believe Bill claims that
>he can make the output code identical given a bit more work.

Is there a particular reason why its worth a lot of effort to make Y
compile efficiently??  More the the point, does anyone have examples
of code that is more elegant (or better in some other way) than, say,
a simple recursive implementation??


Bruce

rar@DUCHAMPS.ADS.COM (Bob Riemenschneider) (11/17/88)

=>   In article <8811161012.AA13091@kleph.ai.mit.edu>, cph@KLEPH (Chris Hanson)
=>   writes:
=>   >Bill Rozas has expended no small effort in the MIT Scheme compiler to
=>   >make the Y combinator produce good results, and these timings are
=>   >evidence of that.  Still not perfect, but I believe Bill claims that
=>   >he can make the output code identical given a bit more work.
=>
=>   Is there a particular reason why its worth a lot of effort to make Y
=>   compile efficiently??  More the the point, does anyone have examples
=>   of code that is more elegant (or better in some other way) than, say,
=>   a simple recursive implementation??
=>
=>
=>   Bruce

Rather than present particular examples, let me make three general points.

1.  Y is elegant for the same reason lambda is elegant: you can define
    and use a procedure without having to give it a name.

2.  The treatment of procedures in Scheme, while better than Lisp, is still 
    not entirely first-class.  Here's an example.  

    Suppose n ==> 2.  After 

			(define n (* n n))

    n ==> 4: (* n n) gets evaluated, and the result gets stored in the 
    location n is bound to.  Now suppose f computes the successor function.  
    If procedure values were treated like numeric values, after

			(define f 
			   (lambda (n) 
			      (if (= n 0) 
				 1 
				 (+ n (f (- n 1))))))

   f would compute the function that returns 1 when applied to 0,
   and 2n when applied to any n > 0: the lambda would be evaluated in
   an environment where f computes successor and the resulting procedure
   would be stored in the location f is bound to.  Of course, that's
   not what happens in Scheme.  The definition is interpreted as: let
   f be a solution (in fact, the least solution) to

		         f = (lambda (n) ...f...)

   Having Y allows you to convert the implicit definition into an
   explicit definition

		(define f (Y (lambda (g) (lambda (n) ...g...))))

   and pretend that procedures are treated just like other values by define.  
   This certainly seems more elegant to me.

3. Having Y around makes Scheme seem more like "an interpreter for 
   extended lambda calculus".

							-- rar

max@polya.Stanford.EDU (Max Hailperin) (11/18/88)

In article <8811170243.AA00199@duchamps.ads.com> rar@DUCHAMPS.ADS.COM
(Bob Riemenschneider) writes in response to a question of why Y is useful:

>1.  Y is elegant for the same reason lambda is elegant: you can define
>    and use a procedure without having to give it a name.

If you write
  (Y (lambda (f)
      (lambda (x)
        ... f ...)))
then you have given the procedure a name, namely f, within itself,
just not externally.  The same can be accomplished with named-lambda
or with letrec.  For example, (letrec ((f (lambda (x) ... f ...))) f).
So Riemenshneider's argument isn't much of an argument, if this is
all he has in mind.

What you can do with Y and not easily with letrec or some such is
write something like (Y f) or (Y (f x)).  Of course, Rozas's optimized
implementation may not be quite as much help here.  Does anyone have a
good use for Y in any context other than surrounding a lambda
expression?


>2.  The treatment of procedures in Scheme, while better than Lisp, is still 
>    not entirely first-class.  Here's an example
>
>    Suppose n ==> 2.  After 
>
>			(define n (* n n))
>
>    n ==> 4: (* n n) gets evaluated, and the result gets stored in the 
>    location n is bound to.  Now suppose f computes the successor function.  
>    If procedure values were treated like numeric values, after
>
>			(define f 
>			   (lambda (n) 
>			      (if (= n 0) 
>				 1 
>				 (+ n (f (- n 1))))))
>
>   f would compute the function that returns 1 when applied to 0,
>   and 2n when applied to any n > 0: the lambda would be evaluated in
>   an environment where f computes successor and the resulting procedure
>   would be stored in the location f is bound to.  Of course, that's
>   not what happens in Scheme.

Riemenshneider seems to be confused here about the store vs. the
environment.  This has nothing to do with the firstclassness of
procedures.  Incidentally, the numeric definition he gives only works
as he states at the top level: as an internal definition it is an
error.

dorai@titan.rice.edu (Dorai Sitaram) (11/19/88)

In article <5145@polya.Stanford.EDU> mxh@ksl.Stanford.EDU (Max Hailperin) writes:
>In article <8811170243.AA00199@duchamps.ads.com> rar@DUCHAMPS.ADS.COM
>(Bob Riemenschneider) writes in response to a question of why Y is useful:
>
>>1.  Y is elegant for the same reason lambda is elegant: you can define
>>    and use a procedure without having to give it a name.
>
>If you write
>  (Y (lambda (f)
>      (lambda (x)
>        ... f ...)))
>then you have given the procedure a name, namely f, within itself,
>just not externally.  The same can be accomplished with named-lambda
>or with letrec.  For example, (letrec ((f (lambda (x) ... f ...))) f).
>So Riemenshneider's argument isn't much of an argument, if this is
>all he has in mind.

Last time, I didn't wait to see Hailperin's response to
Riemenschneider before I posted  m y  own response, which turned out
to be almost a clone. Sorry. Now that H has answered the 2nd half of
R's claim #1, let's consider the 1st half:

Using whatever metric for elegance, Y  c a n n o t  be elegant for the
same [or similar] reason that lambda is. They are not in the same
league. Lambda is a core form; Y, on the other hand, is something
defined  u s i n g  lambda. Y can be compared to [let]rec, though. It
is strictly less powerful than the latter, for [let]rec can define
cyclic data objects, including self-eq recursive procedures, whereas Y
can only define non-self-eq recursive procedures. Y can however claim
elegance on the following point: it requires only one* core form
[lambda] for its definition, whereas [let]rec require two [lambda +
set!]. 

I have used the "elegance" of a construct to mean a combination of 2
factors, the economy of core forms needed to get it going, and the
profusion of other constructs this construct itself produces. The
latter, when applied to {a set of} core form(s) is better known as
"expressiveness".

--dorai

*if you consider  a p p l i c a t i o n  to be a core form, add one to
both contestants.