masak / alma

ALgoloid with MAcros -- a language with Algol-family syntax where macros take center stage
Artistic License 2.0
138 stars 15 forks source link

Implement Python-style bound methods #248

Open masak opened 7 years ago

masak commented 7 years ago

I've concluded that Python has this one exactly right.

$ python3
Python 3.4.3 (default, Nov 17 2016, 01:08:31) 
>>> class D:
...   def foo(self):
...     print("Hi, I'm object {}".format(self))
... 
>>> D.foo
<function D.foo at 0x7f1574d2ad08>
>>> d = D()
>>> d.foo
<bound method D.foo of <__main__.D object at 0x7f1574c8ae48>>
>>> d.foo()
Hi, I'm object <__main__.D object at 0x7f1574c8ae48>

In other words, unbound methods are subs, whose first parameter self is the invocant. Every time you access a method through an object, you get a bound method, which is simply a functional wrapper that looks like this:

unboundMethod = ...;
rememberedSelf = ...;

sub boundMethod(...args) {
    return unboundMethod(rememberedSelf, ...args);
}

Unlike Python, I'm going to go with a method keyword instead of having to explicitly write self as a first parameter. But the result of the method keyword will be a regular function with self thus injected:

# corresponds to the Python `def foo(self)` above
method foo() {
    say("Hi, I'm object " ~ self);
}

Later we might want to generalize this to Python-like descriptors. But one step at a time.

masak commented 7 years ago

Actually, given that

x.y()

in 007 (unlike in Perl 6, JavaScript and Java, but like Python) is grammatically just x.y with a () postfixed, I don't think there's a way we can avoid having bound methods. Not if we want to talk about self inside of them anyway.

In #242 I'm currently lifting the Q type hierarchy, and the above necessity is becoming blindingly obvious.

masak commented 7 years ago

I think the nicest thing about this is that something like

mylist.map(someobject.method)

will just automatically fall out of the deal and Do The Right Thing.

masak commented 6 years ago

I just realized that this could also completely replace :: as a namespace separator.

What today is Q::Statement::If could be changed to Q.Statement.If.

How would one define this in-language? Well, I think it'd make sense to allow nested classes to have this behavior:

class Q {
    class Statement extends Q {
        class If extends Statement {
        }
    }
}

(Classes would be "static" by default, unlike in Java.)

But I realize that this would favor the original author a lot in favor of later authors, who can't necessarily "open up" a class and nest new things in it.

So maybe we should also allow this syntax:

class Q.Statement.If {
}
masak commented 6 years ago

In other words, unbound methods are subs, whose first parameter self is the invocant.

Worth pointing out that this is not possible right now, since D.foo would refer to a foo method on the type object D, which (unlike Python) does not have all the methods instances of D has. At least as far as the latest word on the 007 MOP is concerned.

I do see the advantage of having a syntax for unbound methods, though. Maybe if we free up :: from being a namespace separator (as above), we could have D::foo mean "an unbound foo method on an instance of D".

masak commented 3 years ago

Just dropping this here: something as rare as a regret about bound methods. (Edit: I would summarize this regret as "bound methods interact confusingly with method overloading".)