Methods in SchemeTalk are not special built-in objects, but rather normal objects following a specific protocol. The protocol for methods is that first a method is bound to a self and a super. This returns a bound-method. SchemeTalk provides you with a first-class way of doing this yourself:
> (define hello
(new-class Hello object-class () () ()
((hi () (display "Hello") (newline)))))
> (define hi (hello 'bind 'hi))
Now we have a bound-method hi which encapsulates the behaviour of the hi class-method; which is in this case bound to the hello class, with the superclass of the metaclass of the hello class as starting class for next lookup.
Bound methods can then be executed:
> (hi 'execute)
Hello
Since bound methods are normal objects, we can also rebind self and super.
Now we can put objects polymorph to methods in the method dictionary as a way to change the behaviour of methods. As an example I will discuss an implementation of metamethods; a type of methods equivalent to decorators in Python.
The idea of metamethods is that we want to be able to easily build new types of methods. This we do by having a method which represents the meta-behaviour of other methods. This metabehaviour can be shared by multiple actual methods, and the metabehaviour will govern the way that the actual method will be executed.
As an example, we build a metamethod which wraps around other methods, and always adds “20″ to the result of the actual method:
> (define my-metamethod
(metamethod 'with-meta-behaviour
(method args
(+ 20 (self 'execute-behaviour-with-args args)))))
Our metamethod defines a meta-behaviour which executes the actual behaviour with the arguments passed to the metamethod, adds 20 to the result and returns that value. As you can see, our meta-behaviour will wrap around whatever the actual method will do, and can decide whether or not to execute the real method; and when.
Now this meta-method can be used as follows:
> (define a-test-class
(new-class TestClass object-class (abcde) ()
((the-method
(my-metamethod 'with-behaviour
(method (a) (* a
(self 'get-abcde))))))
()))
So we define a class with one method. This one method is not a normal method, but a method wrapped inside our my-metamethod. Now as a proof that it works:
> (define a-test (a-test-class 'new))
> (a-test 'set-abcde! 10)
> (a-test 'the-method 2)
40
So we get 2*10 in the main method, plus 20 of the meta-method which wraps around the metamethod.
Now more importantly, we go over to the actual implementation of metamethod, since this is not something which is directly supported by SchemeTalk, but rather an implementation built on top of SchemeTalk by just using the extensive meta-object protocol for methods.
First, a metamethod is a class which is instantiated by informing it which its meta-behaviour is. That returns something which can be told which its actual behaviour is. That again is polymorph to a method, and thus should be able to be bound to a self and a super. This bound-method-with-meta result should be executable.
So first:
> (define metamethod
(new-class MetaMethod object-class (meta-behaviour) ()
((with-behaviour (behaviour)
(let ((method (method-with-meta 'new)))
(method 'set-behaviour! behaviour)
(method 'set-metamethod! self)
method)))
((with-meta-behaviour (meta-behaviour)
(let ((result (self 'new)))
(result 'set-meta-behaviour! meta-behaviour)
result)))))
The result of adding meta-behaviour is an instance of a meta-method. The result of adding behaviour to a meta-method is a method-with-meta instances which has a behaviour; and a link back to the meta-behaviour. Now we implement method-with-meta:
> (define method-with-meta
(new-class MethodWithMeta method-class (behaviour metamethod) ()
((get-meta-behaviour ()
((self 'get-metamethod) 'get-meta-behaviour))
(bind (asuper aself)
(let ((bound-method (bound-method-with-meta 'on self)))
(bound-method 'set-super! asuper)
(bound-method 'set-self! aself)
bound-method)))
()))
This method can be bound. That will return a bound method, where super and self are stored in the object. The method is shared between all bound methods.
> (define bound-method-with-meta
(new-class BoundMethodWithMeta boundmethod-class
(method bound-meta self super) ()
((get-behaviour () ((self 'get-method) 'get-behaviour))
(execute-behaviour-with-args (args)
(apply ((self 'get-behaviour) 'bind
(self 'get-super) (self 'get-self))
(cons 'execute args)))
(get-meta-behaviour () ((self 'get-method) 'get-meta-behaviour))
(execute args
(apply (self 'get-bound-meta)
(cons 'execute args)))
(initialize ()
(self 'set-bound-meta! ((self 'get-meta-behaviour) 'bind super self))))
((on (method-with-meta)
(let ((result (self 'basic-new)))
(result 'set-method! method-with-meta)
(result 'initialize)
result)))))
When bound methods are executed, it will actually execute the bound-method-with-meta. Self of the method will not be the actual instance on which you are calling it, but the bound-method-with-meta. That method can however give you access to the actual self, as it is stored as an instance variable in the bound-method-with-meta object. For convenience it provides you with a method execute-behaviour-with-args, which executes the actual behaviour which is bound to our meta-method.