Recent

Author Topic: Procedural type that includes itself as a parameter  (Read 4803 times)

doconnor

  • New member
  • *
  • Posts: 7
Procedural type that includes itself as a parameter
« on: July 15, 2017, 04:58:18 pm »
I'm planning to create a subclass of the new TBaseJSONReader class and I want to create a procedural type that includes itself as a parameter.

TJSONCallBack = procedure(var CallBack : TJSONCallBack) of object;

I understand why this is a problem. At that point compiler doesn't know the size of the type because it doesn't know if is an "of object" procedural type. It there a way to predefine a procedurual type like you can with classes?



Almir.Bispo

  • Jr. Member
  • **
  • Posts: 91
  • CSV Comp DB is the Best NoSQL
    • CSV Comp DB (NoSQL)
Re: Procedural type that includes itself as a parameter
« Reply #1 on: July 15, 2017, 05:53:56 pm »
Look this exemple:

Code: Pascal  [Select][+][-]
  1. procedure my_procedure;
  2. begin
  3. //your code here
  4. end;
  5.  
  6. //pass to other procedure
  7.  
  8. procedure form1.button1click(sender:Tobject);
  9. var MycmdCall:Tprocedure;
  10. begin
  11. MycmdCall := @my_procedure;
  12. end;
  13.  
  14.  
  15.  
« Last Edit: July 15, 2017, 06:02:45 pm by Almir.Bispo »
CSV Comp DB Developer {Pascal Lover}

Leledumbo

  • Hero Member
  • *****
  • Posts: 8744
  • Programming + Glam Metal + Tae Kwon Do = Me
Re: Procedural type that includes itself as a parameter
« Reply #2 on: July 15, 2017, 06:31:17 pm »
I'm planning to create a subclass of the new TBaseJSONReader class and I want to create a procedural type that includes itself as a parameter.

TJSONCallBack = procedure(var CallBack : TJSONCallBack) of object;

I understand why this is a problem. At that point compiler doesn't know the size of the type because it doesn't know if is an "of object" procedural type. It there a way to predefine a procedurual type like you can with classes?
Type size is not a problem, procedural type size is always native pointer size. The problem with this is that you create recursion in the type definition, just as you can't with record. Even if it's allowed, how are you gonna call this? Or declare a method that matches this?

doconnor

  • New member
  • *
  • Posts: 7
Re: Procedural type that includes itself as a parameter
« Reply #3 on: July 15, 2017, 07:35:49 pm »
The idea is that the callback function is called for each key/value pair in the JSON file being parsed. When an object or array value is encountered it can change the callback function used for the duration of the object or array.

This the procedural type I want ot use. (very preliminary)

TJSONCallBack = procedure(JSONtype : TJSONInstanceType; key : TJSONStringType; var CallBack : TJSONCallBack) of object;

guest60499

  • Guest
Re: Procedural type that includes itself as a parameter
« Reply #4 on: July 15, 2017, 09:30:25 pm »
This is impossible, the type expands indefinetly. You can either use an incomplete function type and then cast it, or put the function type in a record and pass around the record.

doconnor

  • New member
  • *
  • Posts: 7
Re: Procedural type that includes itself as a parameter
« Reply #5 on: July 15, 2017, 11:57:27 pm »
Can you provide an example of how it would work with a record?

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Procedural type that includes itself as a parameter
« Reply #6 on: July 16, 2017, 05:00:53 am »
I'm planning to create a subclass of the new TBaseJSONReader class and I want to create a procedural type that includes itself as a parameter.
No idea why you want to do that (really, as later on in this thread you mention the reason). Just pass the type, you require 3 different callbacks anyway. or better yet, let the procedure call itself recursively and stuff handling into that one single/same procedure).

Quote
TJSONCallBack = procedure(var CallBack : TJSONCallBack) of object;
As others mentioned, that isn't going to work because of the recursion.

However, if you insist then there is a way around that:
Code: [Select]
  PSomeCallBack = ^TSomeCallBack;
  TSomeCallBack = procedure(var CallBack: PSomeCallBack) of object;

... and of course, you an always use a generic type definition for the callback function and do a cast.
« Last Edit: July 16, 2017, 05:03:24 am by molly »

guest60499

  • Guest
Re: Procedural type that includes itself as a parameter
« Reply #7 on: July 16, 2017, 05:13:29 am »
Can you provide an example of how it would work with a record?
Create a member of the record with the type you want, and then use that record instead of the function pointer. There doesn't need to be anything else in the record. If what you are doing is implementing a parser you might find this provides a convenient way to associate data with the functions that interpret it.

This works for the same reason that molly's solution does. There is a sentinel type that breaks the recursion. I was trying to figure out how to do it that way but was on a phone.

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Procedural type that includes itself as a parameter
« Reply #8 on: July 16, 2017, 06:13:39 am »
Indeed guest. it is aimed with the same target: breaking the recursion of the declaration. Using a record does the job as well (and might even proof easier to handle/grasp).

However, i would like to point doconnor to an example in fcl-json package as well, named simpledemo.pp.

In there is a subroutine named DumpJSON, that could perhaps give an idea or two:

Code: Pascal  [Select][+][-]
  1. Procedure DumpJSONData(J : TJSonData; DOEOLN : Boolean = True);
  2.  
  3. Var
  4.   I : Integer;
  5.  
  6. begin
  7.   // JSONType property determines kind of value.
  8.   Case J.jsontype of
  9.     jtNull   : Write('Null');
  10.     jtBoolean : If J.AsBoolean then
  11.                   Write('True')
  12.                 else
  13.                   Write('False');
  14.     jtNumber : {JSONNumber has extra NumberType property
  15.                 which determines kind of value (int/float).}
  16.                Case TJSONNumber(J).NumberType of
  17.                  ntInteger : Write(J.AsInteger);
  18.                  ntFloat   : Write(J.AsFloat:10:2);
  19.                end;
  20.     jtString : Write('"',J.AsString,'"');
  21.     jtArray  : begin
  22.                Write('[ ');
  23.                For I:=0 to J.Count-1 do
  24.                  begin
  25.                  DumpJSONData(J.Items[I],False);
  26.                  If I<J.Count-1 then
  27.                    Write(', ');
  28.                  end;
  29.                Write(' ]');
  30.                end;
  31.     jtObject : begin
  32.                Write('{ ');
  33.                For I:=0 to J.Count-1 do
  34.                  begin
  35.                  Writeln('"',TJSONObject(J).Names[i],'" : ');
  36.                  DumpJSONData(J.Items[I],False);
  37.                  If I<J.Count-1 then
  38.                    Write(',')
  39.                  end;
  40.                Write('}');
  41.                end;
  42.    end;
  43.    If DOEOLN then
  44.      Writeln;
  45. end;
  46.  

.. everything nicely tucked away inside one single routine. It does not matter much from which end you wish to implement, whether parsing raw json string-data or already tucked away inside class(es).

doconnor

  • New member
  • *
  • Posts: 7
Re: Procedural type that includes itself as a parameter
« Reply #9 on: July 21, 2017, 03:20:16 am »
I very much would like to keep it all in one routine like molly suggests, but TBaseJSONReader only allows the parsing to go to the next step by returning from its KeyValue, StingValue, etc. methods. Molly doesn't have this problem because she is traversing the JSONData structure, and I'm trying to avoid building that structure.

I got it working with:

Code: Pascal  [Select][+][-]
  1. PJSONCallback = ^TJSONCallback;
  2. TJSONCallBack = procedure(const JSONValue : TJSONValue; CallBack : PJSONCallback) of object;

It's a good solution that doesn't require casting. I had forgotten that you can create pointer types before defining the type.

I found some odd bugs/unintended features.

When I call the callback function I have to use two "@"s

Code: Pascal  [Select][+][-]
  1. Callback(JSONValue,@@NextCallback);

The first one still returns the function pointer. I add two to get the pointer to the function pointer. This probably isn't a bug, just an oddity of the language.

When I try to use a record method as the callback function, I get this error.

Error: Incompatible types: got "TArrayOfObject.Scallback(const TJSONValue;PJSONCallback);" expected "<procedure variable type of procedure(const TJSONValue;PJSONCallback) of object;Register>"

However, when I use a record helper method, it works.

Thaddy

  • Hero Member
  • *****
  • Posts: 14157
  • Probably until I exterminate Putin.
Re: Procedural type that includes itself as a parameter
« Reply #10 on: July 21, 2017, 11:10:44 am »

Error: Incompatible types: got "TArrayOfObject.Scallback(const TJSONValue;PJSONCallback);" expected "<procedure variable type of procedure(const TJSONValue;PJSONCallback) of object;Register>"

However, when I use a record helper method, it works.

Well, add sane mode to your unit instead of quirks mode( the default, alas). Sane mode is {$mode delphi} quirks mode is {$mode objfpc} for people coming from Delphi.
Actually, in this case, {$mode objfpc} is more strict, hence you need to take the address, but in {$mode delphi} the compiler does that for you.
In your own units it is fully transparent. But note in both dialects it has to be a procedure of object as you already discovered..
« Last Edit: July 21, 2017, 11:15:16 am by Thaddy »
Specialize a type, not a var.

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 844
Re: Procedural type that includes itself as a parameter
« Reply #11 on: July 21, 2017, 12:25:31 pm »
So, your task needs some state to be stored, cuz it's actually finite-state machine, like parsers? Then using callbacks - isn't right way to do it. You need some wrapper object to store state, so this state would be available to methods of this object. Something like this:
Code: Pascal  [Select][+][-]
  1. type
  2.   TMyCallback = function(...):Boolean of object;
  3.   TMyWrapper = class
  4.   protected
  5.     FState:TMyCallBack;
  6.     function FirstCallback(...):Boolean;
  7.     function SecondCallback(...):Boolean;
  8.   public
  9.     constructor Create;
  10.     function Execute(...):Boolean;
  11.   end;
  12.  
  13. constructor TMyWrapper.Create;
  14. begin
  15.   FState := FirstCallback;
  16. end;
  17.  
  18. function TMyWrapper.FirstCallback(...):Boolean;
  19. begin
  20.   ...
  21.   FState := SecondCallback;
  22.   ...
  23. end;
  24.  
  25. function TMyWrapper.SecondCallback(...):Boolean;
  26. begin
  27.   ...
  28. end;
  29.  
  30. function TMyWrapper.Execute(...):Boolean;
  31. begin
  32.   Result := False;
  33.   if Assigned(FState) then Result := FState(...);
  34. end;
  35.  
  36. procedure MyCallbackExecutionRoutine(ACallback:TMyCallback);
  37. begin
  38.    while ACallback(...) do;
  39. end;
  40.  
  41. {Example of use}
  42. var
  43.   CallbackWrapper:TMyWrapper;
  44.  
  45. CallbackWrapper := TMyWrapper.Create;
  46.  
  47. MyCallbackExecutionRoutine(@CallbackWrapper.Execute);
  48.  
  49. CallbackWrapper.Free;
  50.  

P.S. Code is more Delphi-like - some adjustments are needed for this code to be compiled via FPC

It can be implemented the way, you initially wanted it to be implemented, via closures only - and they aren't supported by FPC yet:
Code: Pascal  [Select][+][-]
  1. TMyCallback = reference to function(...):Boolean;
  2.  
  3. function CreateCallback:TMyCallback;
  4.   var Callback, OtherCallback:TMyCallback;
  5. begin
  6.   {Other callback - may be used multiple times}
  7.   OtherCallback = function(...):Boolean
  8.     begin
  9.       ...
  10.     end;
  11.   {Initial callback}
  12.   Callback := function(...):Boolean
  13.     begin
  14.        ...
  15.        {Example of immediate callback}
  16.        Callback := function(...):Boolean
  17.          begin
  18.            ...
  19.          end;
  20.        ...
  21.        {Example of multiple use callback}
  22.        Callback := OtherCallback;
  23.        ...
  24.     end;
  25.     Result := Callback;
  26. end;
  27.  
  28. procedure MyCallbackExecutionRoutine(ACallback:TMyCallback);
  29. begin
  30.    while ACallback(...) do;
  31. end;
  32.  
  33. {Example of use}
  34.  
  35. MyCallbackExecutionRoutine(CreateCallback);
  36.  
  37.  
« Last Edit: July 21, 2017, 02:05:19 pm by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

 

TinyPortal © 2005-2018