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

On optional arguments

The discussion on optional arguments at the beginning
stopped before a consensus was reached. Since the last
letter in the thread was posted a while ago, I have
made an attempt to recap the discussion.

The discussion started in <http://srfi.schemers.org/srfi-67/mail-archive/msg00036.html>
in which Mike Sperber writes:

    procedure:  (=? [ compare ] [ x y ])
    procedure:  (<? [ compare ] [ x y ])
    procedure:  (>? [ compare ] [ x y ])
    procedure:  (<=? [ compare ] [ x y ])
    procedure:  (>=? [ compare ] [ x y ])
    procedure:  (not=? [ compare ] [ x y ])

    I dislike having the compare optional argument at
    the beginning: There seems to be almost no precedent for
    it in Scheme libraries, and it means that the parameter
    positions change their meaning depending on the total
    number of arguments, which I find confusing.

In <http://srfi.schemers.org/srfi-67/mail-archive/msg00039.html>
Sebastian Egner writes:

   b) If there is an optional compare proc. why is it in front?

   So that (foo compare x y) is always understood as
   (foo (compare x  y)). This is consistent throughout the SRFI with
   all operations accepting a compare procedure as parameter---no matter
   the arity.

   Personally, I would find it confusing if the compare procedure
   argument is in front for some operations and at the tail for others.

        (if<? (compare x y) A B)
        (if (<? compare x y) A B)
        (if ((<? compare) x y) A B)

   The confusion of compare changing places might be more than the
   confusion of having an optional leading argument.

In <http://srfi.schemers.org/srfi-67/mail-archive/msg00046.html>
Mike writes

   - You use overloading to implement the curried version.

     I think that's a bad idea, partly for the reason described above,
     and partly for the existence of SRFI 26, which makes it clear that
     there's currying going on with little notational overhead.  (But
     who am I talking to? :-) )

   - You ditch the overloading, your rationale goes out the door.

and elsewhere suggests

    Solution #1:
    Ditch the curry overload; make the comparison an optional last

    Solution #2:
    Make the comparison a mandatory first argument.

I'll for a moment assume that we ditch the curry overload
and let the compare argument be the first of any optional
arguments. Do we then get consistency? Well, almost, but there
is still an (unrepairable) conflict with these functions

    (chain=? compare x1 ...)
    (chain<? compare x1 ...)
    (chain>? compare x1 ...)
    (chain<=? compare x1 ...)
    (chain>=? compare x1 ...)
    (pairwise-not=? compare x1 ...)
    (min-compare compare x1 x2 ...)
    (max-compare compare x1 x2 ...)
    (kth-largest compare k x0 x1 ...).

[the conflict being  (=? x y compare) versus (chain=? compare x y) ].

[Note: The if-family of macros also take compare as first parameter]

The main objection against solution #1 is thus that it will become harder to remember where the compare functions are to be placed.

But why did Mike objecti to putting the optional argument in front?

On the technical side in
he writes:

  The problems come later, when you pass these things around as
  higher-order procedures, and it's not immediately apparent that the
  procedure you've been passed uses an unorthodox argument processing
  convention.  (This is generally a case against overloading in
  higher-order languages, I think---it doesn't scale well.)  This is why
  you haven't encountered the problem yet, but may in the future.

I am not sure I understand what he is getting at. If an unknown
function is passed, then you obviously don't know how to call it
correctly - and if you do know it, then you are aware of the
functions calling conventions.

On the cognitive front in
he argues (prompted by the curry overloading I think) that
having different names for different operations is preferrable
(I tend to agree with that).

Also on the cognitive front it should be mentioned that the
traditional way of passing optional arguments is to use
the variable number of arguments-mechanism. One might even
say that our problem is that Scheme doesn't have a way
to specify optional arguments. If it were possible to
specify optional arguments with keywords this whole thread
wouldn't exists. I.e. both (<? :compare default x y) and
(<? x y :compare default) would work.

The argument for the compare argument in front is also
cognitive. The most important one is consistency as
mentioned previously.

Another parameter to consider is code readability.
In some situations it is important to have read a
certain argument before reading the others. Erm - more
concretely consider

  (<? compare x y).

Here <? signals we need to compare something. compare
describes how the things are compared, and x and y
is what to compare. In

  (<? <large-expression1> <large-expression2> compare)

one reads what is to be compared before one finds
out how they are to be compared. In that situation
I prefer

  (<? compare <large-expression1> <large-expression2>)

Botner has a similar point in <http://srfi.schemers.org/srfi-67/mail-archive/msg00050.html>

  SRFI 64 also has non-final optional arguments.  E,g.:
  (test-eqv [test-name] expected test-expr)

  I think this is preferable to having two functions, and
  in this case having the test-name first is desirable for
  documentation reasons:

  (test-eqv "test-string-apppend-1" (....) (....))

To sum up - I think the choice is between tradition and
consistency. In the end it is a matter of taste, which
is why it is hard to choose.

My personal preference is to keep the optional compare
argument in front. Having it appear first in some functions
(chain=?, ...) and last in others will confuse me.

Jens Axel Søgaard