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

informal proposal: A Modified Procedural Interface (LONG)



I think your proposal for variable arity procedures is quite neat, but
it suffers from some problems that we have observed at MIT with our
current optional argument extension.  The most noticeable, which
motivated the "default objects" in Will's proposal, arises from cases
like

(define (make-bracket-unparser core)
  (lambda (object #!optional port radix)
    (write-char #\[ port)
    (core object port radix)
    (write-char #\] port)))

which in the context of your proposal would be written as

(define (make-bracket-unparser core)
  (lambda*

      ((object)
       (write-char #\[)
       (core object)
       (write-char #\]))

      ((object port)
       (write-char #\[ port)
       (core object port)
       (write-char #\] port))

      ((object port radix)
       (write-char #\[ port)
       (core object port radix)
       (write-char #\] port))))

There is a lot of replicated code, and it only gets worse when there
are more "optional" parameters whose values the programmer wants to
default in the procedures invoked.


A related problem is that it is hard to default "intermediate"
parameters.  For example, it is cleaner (I think) to write

((make-bracket-unparser write-number-heuristically)
 23
 (make-default-object)		; = ((lambda (#!optional x) x))
 #x10)

than to write

((make-bracket-unparser write-number-heuristically)
 23
 (current-output-port)
 #x10)

since the latter has only the desired effect if in fact the default
port is the value of (current-output-port).

In other words, the proposal with default objects allows specification
of only those parameters for which we do not want to use the default,
while your proposal forces the user to specify all parameters to the
LEFT of those which s/he REALLY wants to provide.  This is a serious
breach of abstraction, since s/he must know which way these parameters
are defaulted in order to obtain the same effect as if they had not
been provided.


Some other comments with respect to your proposal and your message:

1) You mention that the same objections that hold for returning
multiple values in a list should hold for passing "multiple arguments"
in a list, but you are really confusing two issues here.

One issue is whether the argument passing mechanism and the value
return mechanism (which is merely passing arguments to the
continuation) inherently involves lists, and the answer to this should
clearly be no.  Returning a fixed number of values to a continuation
expecting that number of values should not be very different from
passing a fixed number of arguments to a procedure expecting that
number of arguments.  Implementations are free to use lists of values
(and lists of arguments), but this fact should not be apparent to the
user.

The other issue is how to RECEIVE (by a procedure or a continuation)
arbitrary numbers of values while providing a fixed finite list of
formal parameter names.  Lists happen to be convenient and natural
data structures to "bundle up" many objects into a single object from
which the individual objects can be extracted, yet allowing simple
manipulation of the aggregate.  Many other data structures would do
nicely, but lists are (arguably) the most appropriate for Lisp.


2) Introducing multiple-value locations, and in particular, making
assignment work "correctly" with them makes the implementation of
variables in the language considerably less efficient.  Consider
carefully the implications of the following ugly fragment of code:

(let* ((x 'unspecified)
       (test
	(lambda ()
	  (if x
	      'yes
	      'no)))
       (first
	(begin (set! x 4)
	       (test)))
       (second
	(begin (set! x (mumble))
	       (test))))
  (list first second))

under the following possible definitions of mumble not available at
compile time

;; version 1

(define (mumble)
  3)

;; version 2

(define (mumble)
  (values 3 4))


3) I agree with you that programmers can incur a substantial
algorithmic cost by carelessly using lists in procedures expecting an
arbitrary number of arguments.  For example,

(define plus
  (lambda all
    (if (null? all)
	0
	(binary-plus (car all)
		     (apply plus (cdr all))))))

is quadratic in space (and therefore time), but any clever programmer
will realize that this is a bad program and rewrite it to be linear.

However, the equivalent program

(define plus
  (lambda*
      (() 0)
      ((x & rest)
       (binary-plus x (plus & rest)))))

will probably also be quadratic in space (and time), but it is much less
obvious how to rewrite it to avoid this cost.  To obtain linear
behavior the programmer must rely on a clever implementation (which I
haven't discovered) and is helpless if the implementation is not
clever enough.