[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

&Rest Lists



   Date: Thu, 24 Mar 88 17:12 EDT
   From: ELIOT@cs.umass.edu

      From: Barry Margolin <barmar@think.COM>
      To: ELIOT@cs.umass.EDU

   What I mean by saying that "Using APPLY is probably inefficient
   anyhow" is that you will incur the overhead of a full extra function
   call that probably cannot be optimized away.  Truly efficient code
   requires modifying the arguments so that the list is passed directly,
   rather than using &Rest lists, so you can eliminate the function call
   involved in using APPLY.  

What extra function call?  I would hope that the compiler will inline
code calls to APPLY.  (apply #'foo '(1 2 3)) should generate code at
least as good as (foo 1 2 3), and the only difference between (apply
#'foo '(1 2 3)) and (apply foo '(1 2 3)) should be whether it extracts
the function from FOO's function or value cell.  In the implementation
I use (a Lispm), APPLY is a single instruction.  The only extra
inefficiency that APPLY might incur is the one YOU are advocating --
copying its last argument -- which makes your reasoning circular.

						    If this kind of recursive
	  function has to run quickly it would probably be better to define an
	  auxillary function with a fixed number of arguments to do the real work.
	  Besides, most function calls are not made using APPLY.  Recursive
	  function calls using APPLY are too rare to justify a blemish in the
	  semantics of all function calls.

Well, I know that I use APPLY a whole lot.  Not as often as LIST and
CAR, certainly, but more often than I would have expected.

      It's not just recursive calls.  Consider all the functions that take
      arguments just like FORMAT.  They all call various intermediate
      functions, which call other intermediates, and eventually they call
      FORMAT itself.  The &rest list shouldn't be duplicated at each call.

      Many other functions take &rest arguments that are simply passed on to
      other functions using APPLY.

	  Some might argue that the correct way to disambiguate this situation is
	  to specify that APPLY must share its final argument with any &Rest list
	  argument as much as possible.  This doesn't make sense to me.  I don't
	  believe that it would make Common Lisp more efficient to any significant
	  degree.  I think it creates an undesirable inconsistency in the
	  semantics of function calls, as I argued above.  Furthermore, modifying
	  &Rest lists as a way to indirectly modify some argument to APPLY sounds
	  like one of the worst dirty programming tricks I have heard of in a long
	  time.  The possibility of causing exceedingly obscure bugs makes my skin
	  crawl.

      I don't think anyone is actually suggesting that people intentionally
      modify &rest lists.  Personally, I prefer making it be undefined whether
      &rest lists share, so programmers aren't tempted to write such code.

						      barmar

   I agree that modifying &Rest lists is bad programming style.  But I also
   think that leaving issues undefined is bad language design, unless there
   are very compelling reasons.  Compelling reasons include externally
   imposed constraints on an implementation (forcing us to live with
   ambiguity in the Common Lisp file system operations) and the possibility
   of major optimizations on different types of machines.  Ambiguity in the
   implementation of Arrays allows different types of machines to be used
   for efficient CL implementations.

   The special case of using APPLY to call a function with an &REST
   argument fits neither of these categories.  Anything that can be made
   more efficient by allowing the last argument of APPLY to be shared as
   part of an &Rest list can be implemented MORE efficiently by using a
   normal function call directly.  For example, you suggest that FORMAT
   like functions could not be implemented efficiently.  They could be
   implemented like this:

   (defun format (stream fmt &rest args)
     (format-1 stream fmt args))

   (defun format-1 (stream fmt args)
     ...)

   (defun errror (fmt &Rest args)
     (format-1 *error-io* fmt args)
     (call-debugger))

These solutions only work for built-in functions.  Portable code can't
call FORMAT-1.  I certainly could make non-&rest versions of my
functions, but this means that I must write two functions when I
previously would have written only one, and if I make all my &rest
functions just invoke the non-&rest versions I also incur an extra
function call (like you thought was inherent in using APPLY).

Also, if you were going to do

	(apply #'format str fmt arg2 rest)

and you recoded it to call FORMAT-1, you end up with

	(format-1 str fmt (cons arg2 rest))

Actually, this last argument isn't as compelling as I first thought,
because it only reduces efficiency in implementations that cons &rest
lists on the stack, and we've decided that they are violating the
standard by doing this.

I think these are good efficiency justifications for making the
sharing behavior of &rest lists and list given to APPLY be undefined.

						barmar