[Gambas-user] Interpreter's treatment of classes

Bruno Félix Rezende Ribeiro oitofelix at ...181...
Sat Apr 12 00:27:10 CEST 2014


Em Fri, 11 Apr 2014 20:21:38 +0200
Tobias Boege <taboege at ...626...> escreveu:

> On Fri, 11 Apr 2014, Bruno F??lix Rezende Ribeiro wrote:

> > [...]
> > 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.
> 
> > [...]
> > 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 just want to point out that I'm not arguing that every class should
work as an instance of the class 'Class'.  As you have shown that'd be
impractical.  After all we have the class 'Class' to derive objects
that grants type introspection capabilities, the way it ought to be.
I'm just very concerned about the 'Is' operator which says that every
class is somehow an object which is an instance of the class 'Class'.
That seems to me rather inconsistent.  Notice that 'TypeOf' is a
consistent function as far as I'm aware of: it says that classes are
classes and objects are objects.  In fact, I share with you the opinion
that it's the authoritative reference about such matters. ;-) Now,
having a class named "Class" that is in fact intended for introspection
is a bit confusing for newcomers but it's not inconsistent at all.
Maybe it should be called 'ClassIntrospection' or something to avoid
that.  I think what we can't do is to assert that every class is an
instance of the class 'ClassIntrospection', because it's clearly not
the case, regardless of what the operator 'Is' thinks; either it knows
something about inheritance that I do not or it has a lot to learn with
its cousin 'TypeOf'.  I can't help but ask: am I missing some
metaphysical principle about the way classes are arranged?  Am I,
'Is'?  ;-)

Anyway we do need classes as first-class citizens of Gambas, but we
can't use the name 'Class' for it until we change its meaning as
described above.  The root of all evil seems to be that the expression
'Dim hClass As Class' makes 'hClass' a handler for introspection
objects of the class 'Class', while it actually should be making a
handler for pure classes, as 'MMain', for example.  I think it'd be far
more intuitive, and perhaps yet more useful.  If one wants an
introspection object, they could declare it as 'Dim hObject As
ClassIntrospection'.  Good or not, one consequence of that is that it
would be possible to make multiple aliases for a single class.  But
what's the problem?  Isn't this the current situation for a single
object?


> > 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!

Thanks.  That's the result of the combination of excitement, dedication
and diligence :-P
 

> > [...] 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.
> 

Wow!  That is exactly what I was looking for!  I've looked
into language statements, native functions, operators and classes
and have found nothing.  It just happened to be the wrong place: all the
magic is in a simple casting; a somewhat unpredictable one, though.
Casting is really a subject matter that seems to be almost entirely
overlooked in the language documentation.  I couldn't find anything
specifically about it at Gambas' Wiki.  Is it even an official language
feature?  The only meaningful casting I did know about were those
involving different numeric types, including booleans.

Interesting enough, I've found that casting classes to objects results
in introspection objects for the original classes, so you don't have to
specifically cast them to the class 'Class'.  That's a really
meaningful and useful behavior.  Take a look:

  Dim hObject As Object = MMain

  TypeOf(MMain) ==> gb.Class
  TypeOf(hObject) ==> gb.Object

  hObject Is Class ==> True
  hObject.Name ==> "MMain"

It means that classes are already casted to introspection objects of
the class 'Class' when your function starts.

> > 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.
> 

I think I've failed to convey my idea here.  I'm describing exactly
what you call "the other way around".  After rereading it I can see
how you may have misinterpreted it as the opposite of what I intended
to mean.  Sorry, it's my fault to not have made it clearer.


> > 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.
> 

You are right --- there are arguably better alternatives which are more
object-like.  However, making them first-class citizens is a first step
towards reflection[1], don't you think?  Is Gambas in any way a
reflexive language?

> > 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.

The idea you described above was my first implementation.  Thinking
better now, it's actually a good method.  The flaw I saw within it is
not a real concern; it was just a (perhaps silly) matter of style.

Indeed the 'Eval' function will be of opportune use when I start to
think about functions only known at runtime.

Define my own interfaces?  Hummm... let me see...

> 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.
> 

That's a good idea!  My current implementation does kind of that, I
mean, it defines a Plottee class, which the Plotter class uses for
retrieving the points.  However I was struggling with how to make the
Plottee class instantiate objects which could use arbitrary functions.
So I've resorted to implement it in an obscure way referencing objects
and methods by its name and checking its signatures.  As you have
shown it turns out to not be necessary at all.  Notwithstanding, it was
very useful to learn about the guts of class treatment; the issue which
originated this correspondence. :-)

There is only a down side of this interface method: every new function
needs a entire new class to implement it.  Furthermore it's
only useful for functions known at compilation time.

> There may also be some things you didn't know yet. Feel free to ask.

Thanks, I took note of that.  I won't be shy. :-)
 
> 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.

Yeah, perhaps the initial event-driven interface is actually the best
one.

Thank you so much for the enlightening response and source code
examples.

Happy Hacking!

[1] https://en.wikipedia.org/wiki/Reflection_%28computer_programming%29

-- 
 ,= ,-_-. =.  Bruno Félix Rezende Ribeiro (oitofelix) [0x28D618AF]
((_/)o o(\_)) There is no system but GNU;
 `-'(. .)`-'  GNU Linux-Libre is one of its official kernels;
     \_/      All software must be free as in freedom;
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 490 bytes
Desc: not available
URL: <http://lists.gambas-basic.org/pipermail/user/attachments/20140411/0121f532/attachment.sig>


More information about the User mailing list