When a static method field is accessed, the result is a procedure value. For example :-
import io
class X()
public static f()
end
end
procedure main()
local m
m := X.f
write("type=", type(m))
write("image=", image(m))
end
This program writes :-
type=procedure
image=method X.f
The value of m
is just a procedure that can be invoked when desired.
For an instance method, the situation is a little more involved, because the instance needs to be kept to hand so that it can be passed to the method as the value of the self
parameter. To achieve this, when an instance method field is accessed, a small structure is created which holds two things, namely the instance and the method’s procedure. This structure is called a “methp” (short for “method pointer”).
To illustrate this, consider the following :-
import io
class X()
public g()
end
end
procedure main()
local i, m
i := X()
m := i.g
write("type=", type(m))
write("image=", image(m))
end
The output in this case is :-
type=methp
image=methp#1(object X#1(0),method X.g)
The value m
can now be used just like a procedure to invoke the method :-
m(1, 2, 3)
and the effect of this is to call the method g
with the given parameters, as well as the self
parameter, which will have the value of the instance i
.
One other general point to note about methods is that access permissions are checked when the field is accessed, not when the method is called, which can be done at any later time from any location in the program.
In functional programming languages, anonymous, or lambda, functions, capture a “closure” over the local variables in scope, so that a function carries with it an additional, hidden reference. As mentioned above, method pointers do the same thing, but instead of a hidden reference to a set of local variables, there is a reference to an object instance.
This similarity allows method pointers to do the same sort of things that can be done in functional languages with lambda functions, so long as we think in terms of classes and objects, instead of closures over local variables. For example, consider the problem of writing a function which takes a parameter n
, and returns another function which adds n
to its argument. This can be done as follows :-
import io
class X()
private n
private f(x)
return x + n
end
private new(n)
return self.n := n
end
public static adder(n)
return X(n).f
end
end
procedure main()
local f
f := X.adder(20)
write(f(4))
write(f(6))
f := X.adder(100)
write(f(5))
write(f(10))
end
The output is
24
26
105
110
This technique is used in the ipl.functional
package to provide various high-order procedures commonly found in functional programming languages.
This page describes how, together with co-expressions, method pointers can be used to provide simple lambda-style anonymous functions.
In order to keep garbage collection operations to a minimum, methp
objects are not created unless absolutely necessary. For example, although the expression obj.f(100)
could be implemented by creating a methp
for obj.f
, and then applying that to the parameter 100
, it is more efficient to gather both of these steps into a single operation that avoids the creation of the intermediate methp
. This is implemented in the interpreter with two special virtual machine instructions (invokef
and applyf
).
One point to note is that in an expression like
obj.f(obj := expr)
then it is the value of obj
after the assignment from which the field f
will be evaluated. This is because the whole expression is treated as a unit, and obj
is only dereferenced after the arguments have been evaluated.