[Python-Dev] Re: Multimethods (quelle horreur?)

Samuele Pedroni pedroni@inf.ethz.ch
Sat, 17 Aug 2002 23:47:16 +0200


[generic function (abbreviated gf) is the used terminology
(CLOS, Dylan, goo, research papers) for
a multidispatching function,  and yes
normal Common Lisp functions are as generic
as you can get, still that's the terminology]
 
the question was whether
adding a method to a gf 
is always the moral equivalent of
 
 lib.py:
 
class A:
   def meth(self,...): ...
 
 class B(A): ...
 
 class C(B):
   def meth(self,...): ...
 
 abusive_user.py:
 
 from lib import A
 
 def foo(...): ...
 
 A.meth=foo
 
 > Well, I still don't get it. I clearly don't know what "fiddling" means,
> since any added signature can change the behavior of the multimethod. I
> think I would be inclined to forbid your first case, where you're adding a
> multimethod implementation whose signature exactly matches another one
> > that's already in the multimethod.
 
 The point is whether the behavior is changed in an undetected way
with respect to sets of arguments for which some matching signature/
method is already defined. So my conditions
 
 add(M,(h,T3))  with T3==T2 (*) or T1<T3<T2.
 (assuming that T3==T2 triggers substitution) .
 
 [T3==T2 case corresponds to the above
 
 A.meth = foo 
 
 T1<T3<T2 correspond to the single dispatch case:
 
 from lib import B
 
 B.meth=... ]
 
 If T3 is < or uncomparable with all the signatures
 already in M:
 - you are doing the moral equivalent of overriding
 in the single dispatch case
 - or you are defining the gf for some unrelated
 class hierarchies
 - or some case that was unambiguous
 will become ambiguous and the outcome
 will depend on the rules you choose to
 deal with ambiguity (which is a general 
 problem with multidispatching).
 
 
> > [Btw up to (*), missing a module or calling
> > a generic function before all modules are loaded,
> > load order does not count.]
> The above sentence is completely incomprehensible to me.
 
 Dispatching outcomes are invariant wrt
the order by which you add gf-methods to a gf.
 
 
> > import lib
> >
> > class Sub(lib.Class1):
> >   ...
> >
> > class New:
> >  ...
> >
> > addmethod lib.gf(x: Sub,y: Sub):
> >   ...
> >
> > addmethod lib.meth(arg: Sub):
> >   ...
> >
> > is no different than defining new classes and subclassing and
> > overriding methods. Also the kind of resulting program
> > logic scattering is not that different under normal usage.
 > > I agree.
 
 this was just an example of the incomprehesible theory
above <wink>. So we agree.
 
 
 > >
> > See my posted code for the idea of redispatching
> > on forced types, which seems to me reasonably Pythonic
> > and allows OTOH to choose a very strict approach
> > in face of ambiguity because there's anyway a user
> > controllable escape.
> 
 > Could you please explain your scheme in plain English?
> What is a "forced type"?
 
 the idea is to allow optionally to specify together
with an argument a supertype of the argument and to have
the dispatching mechanism use the supertype instead
of the type of the argument for dispatching:

gf(a,b,_redispatch=(None,SuperTypeOf_b))

the dispatch mechanism will consider the
tuple (type(a),SuperTypeOf_b) instead
 of (type(a),type(b)) for dispatching.
 
 This is the moral equivalent of
 single dispatching:
 
 SuperTypeOf_b.meth(b)
 
 or super(SuperTypeOf_b).meth(b)
 
 and can be used as a kind of "super" mechanism
or more interstingly to disambiguate ambigous calls
on user behalf.
 
 
> > My opinion: left-to-right and refuse ambiguity
> > are depending on the generic function both
> > reasonable approaches.
 
 > I assume that "left-to-right" is some kind of precedence ordering for
> ambiguous multimethod implementations. Can you give an example where that
> would be appropriate?
 
is the default used by CLOS, that simply means that
signature (type tuples) are compared using the lexico-order,

given 
   class A: pass
   class B(A): pass
 
 then (B,A)<(A,B)
 
 you get the same effect as multidispatching
 simulated through chained single dispatching.

> > The proposed notation or whatever should be at most
> >  just syntax sugar:
> >
> > (a,b,c).f(d) === f(a,b,c,d) in general.
> 
> All notations (except really ugly ones) are syntax sugar. What point are
> you trying to make?
 
what I was trying to convoy is that it would
be bad to have multimethod invocation be a special
operation different from the usual function invocation
(which currently is at work also for method invocation).
 
 
> > 5. It is true that once you have multimethods you have
> > the choice:
> >
> > class C:
> >   def meth(...): ...
> >
> > vs.
> >
> > class C: ...
> >
> > defgeneric meth
> >
> > addmethod meth(obj: C): ...
 
 > It now looks like you were trying to say in 3. that multimethods should be
> invokable and definable in the same way as single-methods. 

 no, I was saying that at most
 
 (a,).gf(b) should just be equivalent to gf(a,b) (*)
 
 but  plain a.meth() should still mean what it means today.
 
 But honestly I find (*) unnecessary and ugly.
I'm not even sure one can really disambiguate
such syntax:
 
 (a,b).__contains__(2)
 (a,).__contains__(2)
 
 are valid Python.
 
 >Well, I'm not
> sure that this elegant idea from Dylan is essential or even neccessarily
> good for Python. It's cute, but what does it really buy us?

nothing. My point is that once we have multimethods, 
one has the choice, one can
define classes without normal methods and just use multimethods instead,

 I was not advocating that C().meth() should be equivalent
 to meth(C()) in every respect and that the method definitions
 inside the class definitions should triggers gf-method
 definitions. It would be a disruptive change for Python.

So we agree. 

Multidispatching functions should be an extension of the notion
of function in Python not of class methods. OTOH
class methods are defined by defining functions
inside class namespaces, so it should be possible 
to get class methods also from gfs defined
in a class namespace.
 
 regards.