Autore Topic: Trattamento delle Classi da parte dell'Interprete  (Letto 399 volte)

Offline vuott

  • Moderatore globale
  • Senatore Gambero
  • *****
  • Post: 11.302
  • Ne mors quidem nos iunget
    • Mostra profilo
Trattamento delle Classi da parte dell'Interprete
« il: 11 Aprile 2014, 19:59:51 »
Vi riporto questa discussione apparsa nella M.L. ufficiale:


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

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.  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 came to this issue while writing a method for validation of function
signatures in a component I'm working on.  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?

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

Do you suggest a fourth way of implementing the callback function?  Is
there a standard or ad-hoc way I'm missing?

Thank you in advance.

Bruno
"


" If I have understood correctly you are referring to MMain which is module,
not normal class.
And in modules everything is static automatically.

I will look more closely later.

Jussi
"


" I think you mean they aren't dynamic classes.  Modules are static
classes by definition, aren't they?

Anyway, the reasoning until the paragraph you quoted above is
independent of that attribute of classes; I mean, regardless of
'CLASS-CLASS' being static or dynamic every expression described until
that paragraph works the same way.  In the text I wrote, the differences
between dynamic and static classes only show up in the process of
obtaining a proper object of the class 'Class' which describes the
given class 'CLASS-CLASS', what is perfectly possible for dynamic
classes, but I fail to see how to accomplish the same with static
classes without the knowledge of its name at compilation time.

Bruno
"
« Chiunque, non ricorrendo lo stato di necessità, nel proprio progetto Gambas fa uso delle istruzioni Shell o Exec, è punito con la sanzione pecuniaria da euro 20,00 a euro 60,00. »

Offline vuott

  • Moderatore globale
  • Senatore Gambero
  • *****
  • Post: 11.302
  • Ne mors quidem nos iunget
    • Mostra profilo
Re: Trattamento delle Classi da parte dell'Interprete
« Risposta #1 il: 11 Aprile 2014, 21:50:00 »
...continua...


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

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

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

Codice: gambas [Seleziona]

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

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.

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

> 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
"
« Chiunque, non ricorrendo lo stato di necessità, nel proprio progetto Gambas fa uso delle istruzioni Shell o Exec, è punito con la sanzione pecuniaria da euro 20,00 a euro 60,00. »

Offline vuott

  • Moderatore globale
  • Senatore Gambero
  • *****
  • Post: 11.302
  • Ne mors quidem nos iunget
    • Mostra profilo
Re: Trattamento delle Classi da parte dell'Interprete
« Risposta #2 il: 13 Aprile 2014, 02:54:16 »
...continua...


" 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?

> 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.
> 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 https://en.wikipedia.org/wiki/Reflection_%28computer_programming%29 , don't you think?  Is Gambas in any way a
reflexive language?

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

Bruno
"


" > 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?


All that is because, "is a" has two meanings in Gambas.

1) An object reference A "is a" class B. It means that the class of the
object A is the class B.

2) An expression "is a" datatype. And there are two datatypes,
"gb.Object" and "gb.Class".

"gb.Object" is any expression that returns a non-null object reference.
An object reference internally includes two pointers: a class pointer,
and an object data pointer.

"gb.Class" is any expression that returns a class. It includes one
pointer, the class pointer.

"gb.Class" exists only for internal optimization reasons. I could have
used instead a "gb.Object", with a null object data pointer. But it
would have been slower.

Now, as stated in the wiki, classes are really objects whose class is
the "Class" class. But a reference to a class as an object is not the
same thing as a reference to the class. The first one has methods that
describe the class structure, the second one allows to call the static
methods of the class.

> Do you suggest a fourth way of implementing the callback function?  Is
> there a standard or ad-hoc way I'm missing?
>
> Thank you in advance.


As there is no true function datatype at the moment, there is no true
generic solution. (Internally, the function datatype exists, but all
non-official things do no exist. By the way I told you nothing).

When I need doing these sorts of things, I'm only using dynamic object.
And if I have a static class, I transform it into a auto-instanciable
dynamic class (like Forms objects, for example).

If a class is auto-instanciable, using the class name as an object
automatically transforms the "gb.Class" expression into a reference to
the auto-created internal singleton object. So you exactly have the
syntax you need.

And when I will succeed in implementing a clean global syntax, you will
just have to transform your auto-creatable class back to a static class.

Regards,

--
Benoît Minisini
"


" I think I can see what you mean here.  But I can't quite understand how
have you reached the conclusion that "classes are really objects whose
class is the 'Class' class"?  What is the criteria classes are
satisfying that

1) Makes them objects.

2) Granted (1), makes them instances of the class "Class".

Are the answers to these two questions consistent with the Gambas'
meaning of "object", "class" and "instance"?  Or is that just a
particular exception to the general rule?

Do you see any problem with the perhaps more intuitive and consistent
approach of making 'Class' a primitive type as explained in the model
below?


** Reformulation of the interpreter's behavior regarding classes **

The declaration

  Dim hClass As Class = MMain

would make 'hClass' point to the same class that 'MMain' points to
so that

  TypeOf(MMain) ==> gb.Class
  TypeOf(hClass) ==> gb.Class
  If(hClass = MMain, "Equal", "Not Equal") ==> "Equal"
  MMain Is Class ==> [Error: Unexpected Class]
  hClass Is Class ==> [Error: Unexpected Class]

The errors are the same current raised by the expression

  MMain Is Integer ==> [Error: Expected Integer]

This is consistent because like the type 'Integer', the type 'Class'
would be not a class, and therefore would not make sense as the
right-hand side operand of the 'Is' operator which is intended to check
instantiation regarding inheritance, which is clearly not applicable to
classes in the reformulated model.

On the other hand to obtain the introspection capabilities the
declaration

  Dim hClassIntrospection As ClassInstropection = MMain

would make 'hClassIntrospection' points to the object which describes
the class 'MMain' so that

  TypeOf(hClassIntrospection) ==> gb.Object
  hClassIntrospection Is ClassIntrospection ==> True
  MMain Is ClassIntrospection ==> False
  hClass Is ClassIntrospection ==> False
  hClassIntrospection.Name ==> "MMain"
  If(hClassIntrospection = MMain, "Equal", "Not Equal") ==>
  [Error: Type mismatch: wanted Object, got Class Instead]
  If(hClassIntrospection = hClass, "Equal", "Not Equal") ==>
  [Error: Type mismatch: wanted Object, got Class Instead]

Casting of classes to objects as in the declaration,

  Dim hObject As Object = MMain

would retain the useful behavior of resulting in introspection objects:

  hObject Is ClassIntrospection ==> True

I think this reformulation of the model would avoid the apparent
ambiguity and the exceptional status of classes as objects while aiding
consistency and maintaining introspection features.  What do you
think?  Do you see any inconsistencies or pitfalls within it?

bruno
"


" > I said rubbish, as it already works like that. If you use a gb.Class
> with something that requests an object, the it is automatically
> converted to the Class object *except if* the class is
> auto-creatable. In that case it is converted to the automatic hidden
> singleton instance.


You just gave me the last missing piece of the puzzle!  Thank you so
much!

For a given class named 'CLASS' the expression 'CLASS Is Class'
evaluates to 'True' not because 'CLASS' is an instance of the class
'Class', but because in a object context 'CLASS' gets cast to its
introspection object which in its turn is an instance of the class
'Class'.

It's so obvious and clear now!  The interpreter's behavior is perfectly
consistent and unambiguous with respect to this matter.  I feel I owe
you and the operator 'Is' an apology, as I've suggested the
interpreter's behavior was somewhat inconsistent.  Sorry.

In the end it's very good that this has happened.  Now this subject
is solved once and for all, and available in the mailing list for
posterity.

That said, I still would love to have a "pure" class type.  It'd be like
'Class' or 'Object' but it wouldn't cast classes from 'gb.Class' to
'gb.Object'.  That way we could store classes inside variables, make
them accessible under properties and pass them as arguments to methods.
Something like:

  Dim hPureClass As PureClass = MMain

  TypeOf(MMain) ==> gb.Class

  If(hPureClass = Identity(MMain), "Equal", "Not Equal") ==> "Equal"

  Public Sub Identity(hPureClass As PureClass)
    TypeOf(hPureClass) ==> gb.Class
    Return hPureClass
  End

As far as I can see, it would not disrupt any existing code and would
add a feature to the language that allows some things that are not
strictly possible now, for example the implementation of callback
functions by anonymous references to static or dynamic classes.

However, if you happen to think that although being backward compatible
this change would be too big or not worthy, it'd be sufficient,
although not optimal (or so convenient as it could be), to circumvent
the current limitations by being able to (un)cast an introspection
'Class' object to the class it describes, perhaps by a method or
property thereof, intended for immediate use (a little bit like virtual
classes). So it'd look like:

  Dim hClass As Class = MMain

  TypeOf(hClass) ==> gb.Object
  TypeOf(hClass.Class) ==> gb.Class

  If(hClass.Class = MMain, "Equal", "Not Equal") ==> "Equal"

I don't know if it's possible at all given the current implementation
of the interpreter.  In the case it happens to be possible and
relatively easy to implement, it'd solve the biggest drawback presented
in this correspondence.

bruno
"
« Chiunque, non ricorrendo lo stato di necessità, nel proprio progetto Gambas fa uso delle istruzioni Shell o Exec, è punito con la sanzione pecuniaria da euro 20,00 a euro 60,00. »