[Gambas-user] Interpreter's treatment of classes
Tobias Boege
taboege at ...626...
Fri Apr 11 20:21:38 CEST 2014
On Fri, 11 Apr 2014, Bruno F??lix Rezende Ribeiro wrote:
> Hello Gambas users and developers!
>
> I've been studying Gambas for almost a week and a half and I'm very
> impressed with the simplicity and elegance of Gambas' object oriented
> Basic language implementation. Congratulations to all developers,
> specially Beno??t Minisini. What a superb software development
> environment you've shared with us! I hope I can join you soon to work
> on its development.
>
That's good news.
> In the mean time, I'd like to kindly ask for some clarifications
> regarding the treatment of classes by the interpreter.
>
> Let 'MMain' be the main module of a Gambas program and the
> meta-syntactic variables 'CLASS-CLASS' and 'OBJECT-CLASS' be the
> expressions 'MMain' and 'Class.Load("MMain")' respectivelly. Consider
> the evaluation of the following expressions inside the 'Main' method of
> 'MMain' module:
>
> TypeOf(CLASS-CLASS) ==> gb.Class
> TypeOf(OBJECT-CLASS) ==> gb.Object
>
> As you can see the CLASS-CLASS expression yields a class, while
> OBJECT-CLASS expression yields an object. One might wonder what's the
> class of the latter:
>
> OBJECT-CLASS Is Class ==> True
>
> That's expected as we may presume OBJECT-CLASS evaluates to an object,
> of the class 'Class', which describes the class 'MMain', which in its
> turn is the result of the evaluation of CLASS-CLASS.
With you so far. But, as Jussi pointed out, we aren't talking about classes
in general but - and that may be related to the problem following - about a
Module which is a static class. And it is not at all for granted that they
behave similarly. We can also have a singleton (Create Static) class which
is to be classified as a dynamic class but behaves like an object most of
the time you use the class name.
> Surprisingly
> enough, when one applies the same expression to CLASS-CLASS they
> obtain:
>
> CLASS-CLASS Is Class ==> True
>
> The only possible explanation is that CLASS-CLASS is simultaneously a
> class and an object. However, it doesn't behave as an usual instance of
> the class 'Class'. For instance, it is impossible to access the public
> methods and properties defined within the class 'Class' using some
> expression like 'CLASS-CLASS.SYMBOL' where the meta-syntactic variable
> 'SYMBOL' is a public symbol of the class 'Class'. Therefore, the
> assertion that CLASS-CLASS evaluates to some object which is an
> instance of the class 'Class' is somewhat meaningless underneath the
> usual concept of class/instance of object oriented programming.
>
> My first question is: why does Gambas behave this way? What's the
> reasoning backing up the exception to the general rule that the
> evaluation of 'CLASS-CLASS', while an instance of the class 'Class',
> represents? Would not it be simpler and more intuitive (hopefully
> without loss of technical merits) to take CLASS-CLASS as just a "pure"
> class?
>
I think the notion of class is ambiguous at this point in Gambas - but only
Benoit can tell us if that's true.
Besides the normal class/object model where a Class class would only be used
for reflection, we want to, like, access class constants from the class
name, like Align.Center in the Align class. That would be impossible if the
symbol "Align" referred to an object of the Class class because it then
wouldn't have the possibility to exhibit its own symbols.
On the other hand, it _is_ a class and that's why TypeOf() is right. Basta.
:-)
> I came to this issue while writing a method for validation of function
> signatures in a component I'm working on.
Pretty strange but sure a good way to learn Gambas primitives if you can
deal with it :-) And your first component after a week and half. An
impressive learning curve!
> It works well with methods
> residing in dynamic classes, since given an object 'OBJECT' which is an
> instance of a dynamic class 'CLASS' which implements the method
> 'METHOD', one can easily obtain the method's signature with the
> expression 'Object.Class(OBJECT)["METHOD"].Signature'. However, for
> static classes I couldn't find a way to obtain the signature directly
> from the class object given that
> 'Object.Class(CLASS)["METHOD"].Signature' wouldn't work since
> 'Object.Class(CLASS)' evaluates to the class 'Class' and not 'CLASS' as
> would be desired, and we couldn't use it directly as in the expression
> 'CLASS["METHOD"].Signature' as one would naturally expect after
> pondering about the fact, pointed out above, that 'CLASS Is Class'
> yields 'True'.
>
> The only way I have succeeded to obtain the signature is using the name
> 'NAME' of the static class 'CLASS' within the expression
> 'Class.Load("NAME")["METHOD"].Signature'. That is unfortunate because
> I'm compelled to discriminate between static and dynamic classes, not
> to mention I need to find a way to obtain the name of a static class
> from itself. I thought there could be a more elegant solution. Is
> there? The ideal solution would be to provide a general way to get the
> "true" object of the class 'Class' which describes the class 'CLASS',
> since the fact that 'CLASS' is a "false" (and bastard) object of the
> class 'Class', and therefore doesn't describe its own properties while
> a Class --- but the properties of its objects --- is immaterial to any
> practical application I could think of. Summarizing: currently it
> seems only to be possible to obtain an object of the class 'Class'
> which describes the class 'CLASS' if you have an object which is an
> instance of it; therefore it only works for dynamic classes, and you
> have the burden of instantiation. Maybe I haven't looked into the
> right place. Could you, please, help me?
>
OK, this is tricky (thanks by the way for that very interesting question!).
It seems that TypeOf(CLASS) served you well with MMain. It may not be an
instance of the Class class but you can cast it to one :-)
I attach you a project that, in its heart, uses the following procedure to
print the signature of the first found method in some object's class:
--8<------------------------------------------------------------------------
Public Sub PrintFirstMethod(hObj As Object)
Dim hClass As Class
Dim sSym As String
' Static class?
Try hClass = hObj
' Not a static class, then we can use Object.Class
If Error Then hClass = Object.Class(hObj)
For Each sSym In hClass.Symbols
If hClass[sSym].Kind <> Class.Method Then Continue
Print "Signature of";; sSym;; "in";; hClass.Name; ":";; hClass[sSym].Signature
Return
Next
Print "No method found in";; hClass.Name
End
--8<------------------------------------------------------------------------
It first tries to cast the given object to a Class object which will succeed
if you give a static class, like in PrintFirstMethod(MMain), and will fail
else. If it fails, we can be sure that it is a real object and can use the
Object class.
> Related to this issue is the problem of having a variable callback
> function as a property of some object from a given class. What do you
> think is the best way to setup a callback function for a method? Just
> to make it less abstract: I'm writing a component for plotting
> arbitrary numeric functions. The class 'Plot' implements all the plot
> logic, but it must callback a function, defined by the parent which
> instantiates it, to calculate the plot points. What's the best way to
> implement this behavior? I've tried defining an event for 'Plot' class
> so each time the 'Plot' object would need to (re)calculate the points,
> let's say for a change in the intended interval of the function's
> domain, the event would be raised, so the parent would have to be
> observing the 'Plot' object to intercept the raised event and then do
> the calculation. The problem is that by design the event handlers
> don't return values to the offending object, so the event handler at
> hand would have to make sure of returning it in some other
> pre-established manner, like storing the computed point in 'Last.Y', and
> there would be no check from the interpreter about the implementation
> following the function's signature and returning a 'Float' value, for
> example.
In Gambas, you mostly do it the other way around. If your Plot object wants
data, it would raise its Plot_Data event which needs to be intercepted by
your code. Then you would fill a property of the *Plot* object, like
Plot.Data which you can decide to be a Float or whatever.
You can see this strategy being used rather successfully in the interplay of
Editor and Highlight of gb.qt4.ext or in DataBrowser of gb.db.form and
others.
> So, I decided instead to store the object and method's name
> that implements the mathematical function into the Plot object, so it
> could call it for the computation of points and invariably receive a
> return value. But for that to work correctly I needed to check the
> function's signature. That's why I ultimately came to the issues
> presented above. One initial hope I had was that 'Function' were a
> native type of Gambas, as suggested by the expression evaluation
> 'TypeOf(FUNCTION) ==> gb.Function', where FUNCTION is the
> meta-syntactic variable for a function symbol in the current scope.
> Unfortunately, that turned out to not be true. Is there any reason to
> not make functions first-class citizens? That would solve my entire
> problem from the root, albeit the considerations given above are
> somewhat unrelated and should be considered anyway.
>
Dealing with functions in variables has never been very fertile in Gambas,
if I tried it correctly. I don't know why, maybe because there are better
alternatives, or functions - which are actually methods - don't act very
object-like (they have no methods themselves or data you could manipulate
[ hmm, actually they have a signature... but there are better ways to do
what you want ]).
You need to have an object that just *resembles* a function but isn't simply
an array of bytecode.
> Do you suggest a fourth way of implementing the callback function? Is
> there a standard or ad-hoc way I'm missing?
>
There is the way I described above, there is the Eval() function which _may_
suffice for your needs and there is another possibility: define your
interfaces.
If your Plot object has a plottee (how I call the object providing plot
data), require that the plottee exhibits a specific interface which the
interpreter can check. It works like this:
1) Create a non-creatable Plottee.class and define a method to access
points for the plot from it. This is the base class for all things that
can deliver plot data.
2) In your Plotter.class, request a Plottee object as an argument to get
your points.
Consequently, your user has to define their own Plottee-like class to
interface with your Plotter logic, by inheriting the Plottee.class. The
interpreter will check if they inherits correctly (which includes
overriding methods and properties with the correct signature). Attached
is a project that shows this. If you, e.g., declare _next() in EvalPlottee
as returning an Integer or anything not PointF, the interpreter will raise
a runtime error.
There may also be some things you didn't know yet. Feel free to ask.
This may not be best way. Actually, I find it myself way too Java-like but
I'm hungry and this mail stands between me and dinner. You should also
consider the event-based approach outlined above which is more like how
things are done in Gambas.
Regards,
Tobi
--
"There's an old saying: Don't change anything... ever!" -- Mr. Monk
-------------- next part --------------
A non-text attachment was scrubbed...
Name: module-class-object-0.0.1.tar.gz
Type: application/octet-stream
Size: 4973 bytes
Desc: not available
URL: <http://lists.gambas-basic.org/pipermail/user/attachments/20140411/a81c6620/attachment.obj>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: plotter-plottee-0.0.1.tar.gz
Type: application/octet-stream
Size: 5380 bytes
Desc: not available
URL: <http://lists.gambas-basic.org/pipermail/user/attachments/20140411/a81c6620/attachment-0001.obj>
More information about the User
mailing list