michaliskambi / modern-pascal-introduction

Modern Object Pascal Introduction for Programmers, in AsciiDoc
https://castle-engine.io/modern_pascal
152 stars 39 forks source link

Statement not true #18

Open alpinistbg opened 1 year ago

alpinistbg commented 1 year ago

In 8.2 Callbacks (aka events, aka pointers to functions, aka procedural variables) at the end of page 70 (in .pdf):

Unfortunately, you need to write ugly @TMyClass(nil).Add instead of just @TMyClass.Add.

That's not true.

It didn't occur to me to use class methods for procedural variable of object namely because I know there is no hidden Self in it (procedure of object means it is a <object_reference, method_address> pair).

To my surprise, your example works! But IMO it shouldn't. May be it works because one can invoke a class method using an instance only to specify indirectly it's class and the actual reference is just ignored?

Back to the statement:

Unfortunately, you need to write ugly @TMyClass(nil).Add instead of just @TMyClass.Add.

I've tried:

  M := @TMyClass.Add;

And it works (thus your statement is not true).

Only when Add is a normal method, not a class one, then it won't work and it must be written as @TMyClass(nil).Add, i.e. a reference must be specified.

I'm still confused that it is possible to assign Self-less class method to a procedural variable of object.

Lazarus 1.9.0 r64660M FPC 3.1.1 x86_64-linux-gtk2

Edit: Corrections. There is a Self pointer in class methods according to: https://www.freepascal.org/docs-html/ref/refsu28.html and it points to the VMT. But what I've tried still works.

michaliskambi commented 1 year ago

Thank you for this information!

It seems that FPC since 3.2.0 (at least partially) removed this limitation.

I tested on the attached test_method_callbacks.dpr, that deliberately does @TMyMethod.Add instead in @TMyMethod(nil).Add.

In the ObjFpc mode it definitely fails with older FPC versions. Tested now on FPC 2.6.4, 3.0.0, 3.0.2, 3.0.4 -- all fail like this:

$ fpc test_method_callbacks.dpr
Free Pascal Compiler version 2.6.4 [2014/03/03] for x86_64
Copyright (c) 1993-2014 by Florian Klaempfl and others
Target OS: Linux for x86-64
Compiling test_method_callbacks.dpr
test_method_callbacks.dpr(24,8) Error: Incompatible types: got "<class method type of function(const LongInt,const LongInt):LongInt of object;Register>" expected "<procedure variable type of function(const LongInt,const LongInt):LongInt of object;Register>"
test_method_callbacks.dpr(25,8) Error: Incompatible types: got "<class method type of function(const LongInt,const LongInt):LongInt of object;Register>" expected "<procedure variable type of function(const LongInt,const LongInt):LongInt of object;Register>"
test_method_callbacks.dpr(27) Fatal: There were 2 errors compiling module, stopping
Fatal: Compilation aborted
Error: /home/michalis/installed/fpclazarus/2.6.4-sf/bin/ppcx64 returned an error exitcode (normal if you did not specify a source file to be compiled)

However, since FPC 3.2.0, including 3.2.2, it just works :)

Strangely, I didn't find a mention about this change in https://wiki.freepascal.org/FPC_New_Features_3.2.0 or https://wiki.freepascal.org/User_Changes_3.2.0 . Well, nevermind, the problem is gone.

Unfortunately, it seems it works at simple assignment, but not when method is a parameter. I.e. this still fails to compile, even with FPC 3.2.0 and 3.2.2:


function ActOnCallback(const A, B: Integer; const M: TMyMethod): Integer;
begin
  Result := M(A, B);
end;

...
Writeln(ActOnCallback(10, 20, @TMyClass.Add));

So it is inconsistent now. One has to use the hack @TMyClass(nil).Add when passing a parameter.

I'll update the page shortly, and also submit FPC bugreport about it. It should fail consistently, or be accepted consistently (also in parameters).

I'm still confused that it is possible to assign Self-less method to a procedural variable of object.

This possibility (using class method for of object callback type) is because class methods actually also have a Self pointer, but it points to the class and not the instance.

This pointer allows to use virtual class methods from the class function/procedure implementation, because at runtime you know the exact class. The class methods that are passes are also allowed to be virtual.

See attached test_class_method_self.dpr for what is enables.

As a side-effect, it also makes class methods and regular (instance) methods binary compatible. They both get extra pointer, in one case it is just a pointer to a class, in another a pointer to instance. From what I understand, when calling, the pointer to instance is also effectively a valid pointer to a class (though I couldn't find anymore exact spec how it is internally done, though I can imagine some ways).

This is an extra gain, because it means one can use class method as a value of of object, and it just works.

This is different in static class methods, described on https://castle-engine.io/modern_pascal#_static_class_methods , that indeed have no Self and thus their call is incompatible from calling of object, i.e. they expect one less internal pointer. The static class methods are similar to regular (non-class) routines in this sense, i.e. calling them is just like calling a non-class routine.

See also

Again thanks for reporting this, I'll follow up with FPC report (to make it consistent) and updates to the "Modern Pascal Introduction" text.

michaliskambi commented 1 year ago

Attaching the tests I mentioned above.

test_class_method_self.dpr.txt test_method_callbacks.dpr.txt

Rereading, I also see you found that Self is indeed present in class methods. I hope that my explanation is still helpful :)

alpinistbg commented 1 year ago

As a side-effect, it also makes class methods and regular (instance) methods binary compatible. They both get extra pointer, in one case it is just a pointer to a class, in another a pointer to instance. From what I understand, when calling, the pointer to instance is also effectively a valid pointer to a class (though I couldn't find anymore exact spec how it is internally done, though I can imagine some ways).

AFAIK the first entry into the instance memory is a pointer to VMT. Generalizing, classes can be thought of as a single instances of their meta-class, so that is a nice uniformity.

I'm working on a translation of your book, currently somewhat about 40-50% done. I've got some small (linguistic) troubles but that's quite normal. As a LT Pascal practitioner, back to TP3.0, I am nicely surprised to be able to discover some new things in that text.

Regards,