i_am~super

This feature was implemented a long time ago and is available on every branch of Copper

Creating classes is an interesting paradigm (should be spelled “paradym”, but nobody asked me). Many languages implement such a feature (C++, Ruby, PHP, etc.), some languages make the feature so terse it’s hard to recognize without reading docs, some languages don’t even bother with it, but you can still implement it with some work. There have been a number of arguments about whether closures and classes are essentially the same thing with different names, but I’m on the side that thinks otherwise. It’s a matter of construction, and certainly closures are far easier to create in some languages than in others. I won’t go into a discussion of class versus closure, but I will admit that the line is certainly very blurry, and Copper adds another dimension to it.

Suppose I need to implement a class. What does a class entail? Classes have two things: members and methods. (My personal definition is that methods are functions associated with only a single class (albeit shared by inheritance/extension) and require either the class name to access it (either directly or via a variable created from the class) or some pointer to the function.) Members hold other objects. Since my language doesn’t bother with variables holding anything but functions, these two aspects are combined.
Here’s some quick code:

MyClass = {
	this.a = "hello world"
	this.write = fn(me~this) { # in the new branch: write = [me~this] { #
		print(this.me.a())
	}
}
MyClass()
a = MyClass

Here’s an interesting predicament: What is “me” in the variable “a”? Turns out, “me” references the variable “MyClass”, rather than “a”, which should make sense if you’ve been following how pointers work in Copper. The “this” isn’t any more special than any other pointer. It just so happens to be pointing to “MyClass” when used in the function constructor for “write”, which may actually be what we want it to do. This just happens to be an exception case where we would like to reference “a” or whatever variable “MyClass” is copied to. One optional solution is simply calling the function to reset the self-reference pointer (“this”):

a()

The downside of this is that, if you write a class with builder-methods, all of your values are erased. In other words, anything of the following form would fail:

MyClass = {
	this.init = fn(me~this, x) { # in the new branch: this.init = [me~this, x] { #
		this.me.x = x
		ret(this)
	}
}
MyClass()
a = MyClass.init(10)

The erasing occurs when you call the function stored in “a” in order to set the “this” pointer to “a”.
To workaround this problem, it is required that, like every class, the function on the stack receives a pointer to the variable whose scope is open (up one level). This allows us to simulate classes. Notably, however, such a pointer would not work on the stack’s second-level functions, since they have no parent – only the global namespace comes before them. This pointer would work on any higher level functions, but it could not be chained because it would only access the persistent scope rather than the temporary scope where it is stored and found. The nicest part is that this pointer can be guaranteed to always point to the same function, so long as you don’t perform the same abuse as with “this” in the first example.
For whatever crazy reason, let’s call this special pointer “super”. Conceptually, this is at odds with stack terminology (since the stack supposedly builds “up” not “down”), but with regards to the nesting of functions, it is usually helpful to think of the parent function as what is “above” or at least “before”. (Incidentally, after adding this feature to Copper, I noticed that “super” is a not-yet-widely-supported feature of JavaScript that works similarly to the Copper version. I say that without yet having checked Safari and Opera.)
Here’s an example of how it would work:

MyClass = {
	this.a = "hi"
	this.write = {
		print(super.a)
	}
}
MyClass()
a = MyClass

Notice that, in this case, there is no parameter that needs to be passed to the function “write”. This is actually rather convenient in more ways than simply class creation because it also means we can very easily copy such a method and make it a member of completely different function-objects than the function from which it came:

OtherClass = { this.a = "what?" }
OtherClass()
OtherClass.write = MyClass.write

Alternatively:

OtherClass = fn( a = "what?" ) # in the new branch: [a="what?"] #
OtherClass.write = MyClass.write

Doing this means that “super” now accesses the persistent scope of the function stored in “OtherClass” when that function is called. Consequently, these lines print the members of their respective objects:

MyClass.write() # prints "hi" #
OtherClass.write() # prints "what?" #

“super” refers to the function whose scope is open. This is important because it means that you can safely call the same function via pointer and not have it mess up. i.e. The following lines print the exact same thing twice:

MyClass.write()
a~MyClass.write
a()

As I said before, chaining is restricted due to this special pointer being added to the temporary scope, so the following method creates a function-object called “super” in the function-object above the topmost method:

a = { this.b = {
super.super = "Hello"
}}

It’s not harmful to do this as the pointer will be overwritten the next time a user-created function is run. On the downside, it can make for some brief and tricky code obfuscation, as in the following:


a = {
	this.out = false
	this.transient = {
		super.super = true
	}
	this.transient()
	this.out = this.super
}
a()
print(a.out())

Admittedly, this is a peculiar phenomenon, but it’s all in accordance with the rules. The special pointer must be added to the temporary scope in order that its variable be deleted when the function ends and that it be reset the next time the function is run again. The special pointer accesses the persistent scope because that’s generally what we want. It makes very little sense to create a pointer to the temporary scope one level up on the stack, especially since such objects are going to be erased when their encompassing function ends. However, by accessing the persistent scope, the special pointer is not able to access the special pointer of the lower stack level. This isn’t a total loss, though it may be confusing initially for those expecting otherwise. In short, it’s not a really a special construct that follows its own set of rules; it’s merely a pointer that just so happens to provide a convenience.
Like the “this” pointer, you can treat it like any other pointer, so it can be used to allow pointing to a root function that is intended to act like a class.
Like the “this” pointer, there are some things that result in unintended consequences. As I mentioned before, it can undergo a similar odd uses as the “this” pointer. For example:


a = {
	this.b = {
		this.c = fn(parent~super) {} # in the new branch: this.c = [parent~super] {} #
	}
	this.b()
}
a()
d = a
d()

Because initialized variables are not refreshed until their function is called, this results in “parent” being tied to “a” only if “a” is called first. Regardless of whether or not “a” is called, “parent” is set to “d” after the call to “d”.

Implementation

The implementation turned out to be so easy that it almost makes little sense to devote a section to discussing it. When accessing a member of a variable, I saved the parent variable (of that member) and made it the super pointer. That should serve as a hint for how to use it.

Conclusion

In conclusion, it is quite possible to develop flexible classes using the special “super” pointer. The rules are alittle funny, but I believe they don’t inhibit readability. If anything, they save complicated messes that would have otherwise resulted from various attempts to work around the rules. There’s no need for such nonsense. If there’s a good enough reason to implement a feature, it should be implemented. Enough said.
Notably, there is a “super” in JavaScript that allows doing something similar, but due to the rules of JavaScript, it’s much MUCH more of a pain to do this, and for that reason, the creators of CoffeeScript decided to implement an entirely new “class” feature that expands into JavaScript and mimics classes.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s