The self reference (an object's way to refer to itself) seems to be handled very differently across object oriented languages. While in static java-like languages the this keyword is magic, straightforward and mostly unused, in Javascript the magic can be confusing. Yet again in Python some would describe it as "redundantly explicit". In another dynamic language, Ruby, the self acts very similarly to Java's this, but methods aren't objects there either. Is this variety of behaviors due to random design decisions, or is there some common pattern in all these OO languages?
I'd like to propose a thought experiment. We're going to try and create an OO language that has some comfortable features from the languages above. While creating this language, we're going to encounter some situations which will hopefully make it very clear why the self reference is handled so differently. Also, we'll see how the self reference fits in with other features of our favorite languages.
The language we're creating is going to be a statically typed language, but we'll allow dynamic replacement of all members if they maintain the static contract. In other words, just like we could change an integer attribute's value from 4 to 5, we can redefine what methods do, at runtime, as long as the signature (formal parameter names, types, return type, etc.) stays the same. This gives us all the compile type safety we need, but does allow for some black magic if we really want to. We'll also have functions, because we sometimes like those, and they will be objects. To add some spice, we'll make methods first class citizens and objects too, since we'd like to pass those as arguments (observer pattern with just a callback).
There will be no visibility modifiers, those are irrelevant to this argument: everything's public.
If any confusion arises over the word 'object', let's keep it simple: objects are instances of classes. In more detail, objects have a special relation with another entity, which describes (at least in part) the behavior and structure of the given object.
Let me sum it up:
static typing
objects are instances of classes
classes have methods
classes, methods and functions are objects
Let's see it, right? Here's our first class, appropriately named First.
class First {
int x
Constructor(int x){
this.x = x
}
int double_the_fun(){
return 2 * x
}
}
This should seem familiar to those working in Java-inspired languages. We defined a class with one data attribute that's going to be an integer value, and we set its value in the constructor. We also have a method that returns this attribute's value multiplied by 2. Already in this simple example we needed a way to reference the object's attribute (at least) from inside its constructor. Why was it necessary? Because we are shadowing the attribute x with the formal parameter of the same name. Therefore here, the self reference is used to access a scope that's the bag of attributes of the object.
I promised free functions, so here's one:
int triple_the_fun(int x){
return 3 * x
}
I also promised methods could be dynamically replaced, so if we just forgot for one second all that OOP has taught us, we'd do this:
First.double_the_fun = triple_the_fun
We have treated the method double_the_fun just like we'd treat a simple data attribute (integer for example). A little weird for some of us, I know.
We could conceivably call this new method like this
var my_instance = new First(3)
my_instance.double_the_fun(3)
# ar returna 9
Works, right? Nothing seems off except for the fact that maybe we'd have liked for the new method, double_the_fun to use the value of x that's already stored on the object. We'd like my_instance.double_the_fun() to return 9 without passing an integer, or doing ugly things like my_instance.double_the_fun(my_instance.x).
The next move I'll try to do will make all hell break loose, so it's important: What if we had put the this keyword inside the definition of the original function triple_the_fun?
Well we promised static typing at least for what's visible before runtime.
Ei bine, am promis scriere statică cel puțin pentru ceea ce este vizibil înainte de perioada de rulare.
int triple_the_fun(){
return 3 * this.x
}
So what's wrong with this function now? It turns out that in any language, and especially in statically typed ones like ours, this is nasty, if not right out impossible to actually do, because we promised that functions are also objects. Being objects, means they also have a class, let's say the Function class, which also has attributes and methods. So would the this keyword refer to members defined in the Function, or in the First class?
Let's try to fix this ambiguity, and propose some alternative resolutions:
Accept that this refers to 2 inheritance chains. Imagine we had subclassed both classes: Function and also First (chain 1: Function subclasses ->...-> Function and chain 2: First subclasses ->...-> First). We'll just get the this.x attribute from wherever it appears (giving priority to one of these scopes). The problem here is that if the attribute is found in both scopes, we've just lost a way to get one of them. The Java-like explicit syntax First.this.x vs Function.this.x can't help us too much because when the function is not set as a method all the code in it must be valid before runtime too (and First.this.x wouldn't). Also, the type of the x attribute might differ for its definition in the Function versus the First class, so the code would either break when using the object as a function or as a method. Accepting 2 inheritance chains might work better in dynamic languages, but still ambiguity is harder to debug. No language that I know of does this.
Accept that this situation happens and introduce 2 magic keywords. Let's have the keywords caller and this. The this keyword would point to the Function class and the caller would point to the First class. The caller could be null, since functions could become bound, and methods unbound at runtime. This is not that nice to do in statically typed languages, because caller could take any type, and we'd have to check for its type and cast it before usage. It is however doable, but I don't know any language that does this either.
Just choose one of those scopes and forget about the other one. It turns out that Javascript does something very similar to this. As many might know, in Javascript, this always refers to the scope of its caller object (or the global window object), not that of the original object where the function was defined as an attribute. C#'s delegates, and Ruby's Procs and lambdas go the other way. They always remain bound to the class they were defined in (also delegate instances aren't real objects, so this could never refer to any delegate methods/attributes. Ruby procs and lambdas are real objects, but they are created in such a way as to never refer to themselves with the magical self).
Drop the magic of this. Make it just a regular parameter, and also, call it whatever you like. As some might know, Python does this. This solution is inconvenient to some because every method and function that is intended to be used as method, must accept an additional argument.
I hope it's a little clearer now how all these languages tried to solve the same problem, and simply came up with different solutions.
Before finishing, I'd like to bring up one more interesting problem: nested definitions. What happens when a class is defined inside a method, that's defined inside a function, that's defined inside a method and so on? The magic of the self reference begins to fade away in situations like this, since we'd need some additional, more complex syntax to access any enclosing scopes. Java has the ClassName.this.attribute syntax, and other languages might have something similar, but that only partially solves the problem. Ultimately however, the complexity of the scope nesting makes the usage of a magical self reference hard. At that point, the solutions for trying to access the enclosing objects (like the known var that = this; in Javascript) come to resemble Python's very explicit verbosity. Any reference to enclosing objects will be handled explicitly in situations like this. Perhaps it is not by coincidence that Python is such a reflexive language then. Paying the price of verbosity, it models a lot more things as objects, without worrying about the self reference at all.
In conclusion, I wanted to show that the diversity of the implementations is not a purely random or exotic choice, but was influenced by a genuine problem. This limits the decision about what can be modeled as an object in an OO language, and how scopes are referred to. Also, I don't want to endorse any particular language's way of implementing OOP, as I'm sure the problem constraints will make all implementations have both pros and cons.