Newsgroups: comp.lang.dylan,comp.lang.lisp,comp.lang.clos,comp.lang.c++
Path: cantaloupe.srv.cs.cmu.edu!das-news2.harvard.edu!news2.near.net!howland.reston.ans.net!swrinde!cs.utexas.edu!news.sprintlink.net!EU.net!julienas!news.fnet.fr!ilog!news
From: davis@ilog.fr (Harley Davis)
Subject: Discriminating on number of args (was: Two Dylan Questions)
In-Reply-To: sef@CS.CMU.EDU's message of 4 Jan 1995 23:58:42 GMT
Message-ID: <DAVIS.95Jan5171143@passy.ilog.fr>
Lines: 129
Sender: news@ilog.fr
Nntp-Posting-Host: passy
Organization: Ilog SA, Gentilly, France
References: <rloD1tKCG.7D6@netcom.com> <rloD1wGus.8y2@netcom.com>
	<3efcni$gtn@cantaloupe.srv.cs.cmu.edu>
Date: 05 Jan 1995 16:11:43 GMT
Xref: glinda.oz.cs.cmu.edu comp.lang.dylan:3115 comp.lang.lisp:16276 comp.lang.clos:2684 comp.lang.c++:106038


** Note: In this article, I use the terms Lisp, CLOS, Dylan, and
   dynamic language interchangeably.  Please don't be offended.  I
   would have used OODL, but that would include SmallTalk too, and
   what I want to refer to is CLOS-derived OODL's.

In article <3efcni$gtn@cantaloupe.srv.cs.cmu.edu> sef@CS.CMU.EDU (Scott Fahlman) writes:

   It is very hard -- perhaps impossible -- to design a precise,
   principled, and intuitive set of multiple inheritance and precedence
   rules when the methods under a given GF are dispatching off different
   numbers of arguments.  (I beat on this for a while, and then gave
   up.)

It's interesting to look at how C++ works to see if we can't adapt it
for dynamic languages.

C++ provides a pretty simple model for doing this:

(Terminology has been translated into CLOS.)

* First figure out which generic function you're looking at by
  indexing off the class of the first argument.  Each class hierarchy
  defines its own generic function for a given name.

* Divide up all your generic function's methods according to number of
  args into M1, M2, etc.

* For methods with r required and o optional args, add separate
  methods for r+1 r+2 ... r+o-1 methods.  The new methods all call the
  r+o method, passing along the optional arguments' default values
  (which is always a constant).

* For calls with n arguments, treat Mn as a separate gf and do
  discrimination within it.  (Normal gf in the context of C++
  functions does imply some differences in discrimination.)

This is all done at compile time in real C++ implementations, but
that's not an intrinsic part of the model.  For example, in Ilog
Talk's C++ interface we do most of this dynamically to figure out
which C++ entry to call from Lisp.

The biggest change from the current CLOSian vision is that the first
argument is more important than the others.  I would claim that most
CLOS programmers program this way anyway.  Note that dividing up
generic functions according to the first argument doesn't necessarily
preclude multimethods either, all though if completely specced out it
would change the way call-next-method works for multi-methods.  I
would again claim that most CLOS programmers don't use
call-next-method in ways that would break under the new rules, because
the current behavior is already fragile and difficult to understand.

To make this sort of model work in Lisp (or weirdly syntaxed
derivatives), you also need a rule for methods with &rest lambda
lists.  As a strawman, I would propose putting them all into a (or
several) separate method group(s) Minf to be treated after the other
method groups.  In other words, given:

   (defmethod foo ((x <...>) rest: args) ...)
   (defmethod foo ((x <...>) (y <...>)) ...)

   (foo a b)

The second foo method would be called.  The first would only be called
if there were more than two arguments.

Having implemented something very similar for Ilog Talk's C++
interface, I'm pretty sure a scheme like this would work technically,
and that it wouldn't cost anything if not used (except in a little
extra optimization code).  Whether it would satisfy users is a
different issue.  Although I initially believed it wouldn't, I am
coming around to the view that it could be an attractive alternative.

I think Mark Chu-Carroll pointed out that the vision of generic
functionss in CLOS is that each one has one defined behavior
implemented differently for different domains.  I think this is true.
The original poster wanted to reuse the same name for widely varying
specs, and this does require different names in the CLOS software
engineering model.  It's supposed to make code easier to read and, as
Scott Fahlman points out, be tractable in the presence of multimethods
and multiple inheritance.

Another way to put this vision might be that the generic function
definition itself should specify the generic function's return value
type.  Then we can clearly see that "open", for instance, should
always return a <stream>.  If you want something like "open" which
returns something else, you just gotta give it a different name.

C++ looks at the world differently than CLOS&co because of member
functions, which let the receiving object ("this") serve as a scope
resolver.  So you only have to worry about finding unique names for
your classes and not your functions.  This is certainly an advantage
of C++ in the nomenclature department.

It's also possible that even under the current model Dylan's modules
could help reduce name overloading in early development or for
unexported functions. However, Dylan's modules, somewhat like
CommonLisp's packages, seem to require annoying manual intervention
for renaming on import the day you want to use two "open"s in the same
module.  So, in the current Dylan, it may be better to require library
developers to find reasonable but globally useful names right from the
start, rather than require it of the library clients.

Scott Fahlman stated his answer negatively for Dylan instead of C++.
If I were a Dylan advocate bent on replacing C++ with some dynamic
language, I might have said instead that languages which support
discrimination on the number of arguments (ie C++) tend to have other
really annoying restrictions to make it feasible - for instance,
single arg dispatch, constant optional arg values, static dispatch on
# args (ie no apply function), and so on.

However, this would have been unfair.  I actually believe that both
C++ and Lisp-like languages have their place, and C++'s system is
quite interesting because it allows a great deal of functionality with
maximum static checking and resolution.  It's a kind of local
polymorphism optimum.  It's even possible that adopting a dynamic
version of C++'s model as I've outlined above would in fact better
serve Lisp programmers, all things considered.

-- Harley Davis
-- 

------------------------------------------------------------------------------
Harley Davis                            net: davis@ilog.fr
ILOG S.A.                               tel: +33 1 46 63 66 66
2 Avenue Gallini, BP 85                fax: +33 1 46 63 15 82
94253 Gentilly Cedex, France            url: http://www.ilog.fr/

           Ilog Talk information: info@ilog.com
