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

Re: Curl, multiple inheritance, and interfaces



Zooko writes:
>  "Anton van Straaten" <anton@appsolutions.com> wrote:
> > If you're suggesting that simply because a class happens to include
> > methods that match a particular interface, that it should satisfy
> > that interface, I don't think that's a very good idea.  It is what
> > Smalltalk-like languages effectively do, though, but that's only
> > because they're not being explicit about interfaces at all, and
> > because they only support single inheritance so are forced to
> > "cheat" to support multiple interfaces on an object.
> 
> When I switched to programming in Python instead of C++ and Java, I
> learned this style of interfaces, and quickly grew to love it.  I
> can't speak for the designers of Smalltalk, but if I design my own
> programming language, it will definitely include this style of
> dynamic, structural typing as a feature -- not as a work-around for
> some other inadequacy.  ;-)

I agree.  I often wish it were a little more explicit, though --- in
documentation, if nowhere else.  It's often hard to figure out what
methods you need to implement to make an object acceptable to some
piece of code.

See also http://www.shindich.com/sources/patterns/implied.html "The
Implied Interface Pattern", which is a paper on this style of
interface.

Python, by the way, does support multiple inheritance.

It's really nice not to have to drag in all the stupid cruft some
moron has stuck in the base class you have to inherit from, and to
have things just work as soon as you implement the same interface that
base class does.  It also means you can do Cool Stuff, such as having
the same class act as a proxy for multiple different classes that
speak different protocols.  Here's a useful Python class that does
just that, although its usefulness, in an unfortunate self-referential
twist, stems from the convention of not doing much to declare
interfaces:

class protorecord:
    def __init__(self, delegate):
        # TODO: use a proxy object that translates __add__ and
        # __getitem__ etc. into the underlying Python operations
        # if 'delegate' is not an instance
        self.protorecord_delegate = delegate
        self.protorecord_successful_getattrs = {}
        self.protorecord_unsuccessful_getattrs = {}
        self.protorecord_successful_setattrs = {}
        self.protorecord_unsuccessful_setattrs = {}
    def protorecord_report(self):
        return {'successful_getattrs':
                self.protorecord_successful_getattrs.keys(),
                'unsuccessful_getattrs':
                self.protorecord_unsuccessful_getattrs.keys(),
                'successful_setattrs':
                self.protorecord_successful_setattrs.keys(),
                'unsuccessful_setattrs':
                self.protorecord_unsuccessful_setattrs.keys()}
    def __getattr__(self, attr):
        try:
            rv = getattr(self.protorecord_delegate, attr)
            self.protorecord_successful_getattrs[attr] = 1
        except:
            self.protorecord_unsuccessful_getattrs[attr] = 1
            raise
        return rv
    def __setattr__(self, attr, val):
        prefix = "protorecord_"
        if attr[:len(prefix)] == prefix:
            self.__dict__[attr] = val
            return
        try:
            rv = setattr(self.protorecord_delegate, attr, val)
            self.protorecord_successful_setattrs[attr] = 1
        except:
            self.protorecord_unsuccessful_setattrs[attr] = 1
            raise
        return rv

You could, however, use the same technique to record sequences of
method calls strace-like.

> The same notion of structural typing can be implemented with static
> typing, if you like.

OCaml does this; object types are partial mappings from method names
to method types.  The type of object a particular piece of code
expects to be passed is inferred from what methods it calls on it and
what other pieces of code it is passed to.  So I can say:

   let x = fun y -> y#foo + y#bar ;;

which is equivalent to the Python

   x = lambda y: y.foo() + y.bar()

and which responds when entered at the REPL:

val x : < bar : int; foo : int; .. > -> int = <fun>

It knows that foo and bar return ints because their return values are
being passed to +, which has type int -> int -> int, and so it knows
that the argument 'y' must have these methods.  So I can pass it an
object of type <bar: int; foo:int>, or an object of type <bar: int;
foo: int; baz: int -> string>, or whatever, as long as it implements
the methods required with the required type signature.  You could 
create a class of such objects as follows:

   class foobar = 
     object
       method foo = 3
       method bar = 5
     end;;

And you can instantiate objects from it with "new foobar".

OCaml, like Python and Smalltalk, lets you create new classes that
implement the same protocol either by subclassing (inheriting the
superclass's implementation of the protocol, possibly overriding some
or all of it) or simply by implementing it anew.

I think OCaml may be off-topic if we're talking about "lightweight
languages", though.