Jump to content

pmcgee

Members
  • Content Count

    38
  • Joined

  • Last visited

Posts posted by pmcgee


  1. Something I posted on the ADUG forum ... this C code (when I saw it) was posted on twitter as an amusing post about C.

     

    Cursed C code ]

     

    But after I'd thought about it for a bit, it occurred to me that maybe I could do the same in Delphi.

    And we can. 🙂

     

    {$APPTYPE CONSOLE}
    program Project1;
    
    type
      range_struct = record
          type
             Tstate = (at_start, in_loop, done);
          var
             stop, step,
                i, start : integer;
             state       : Tstate;
    
          function    resume()  : integer;    
          constructor λ ( _start, 
                          _stop : integer; 
                          _step : integer = 1);
      end;
    
      constructor range_struct.λ( _start, _stop, _step:integer);
      begin
          start := _start;
          stop  := _stop;
          step  := _step;
          state := at_start;
      end;
    
      function range_struct.resume() : integer;
      label α,β,δ;
      begin
               case state of
                    at_start : goto α;
                    in_loop  : goto β;
                    done     : goto δ;
               end;
    
         α :   i:=start;
               repeat
                   begin
                       state := in_loop;
                       exit(i);
         β :           i     := i + step;
                   end
               until i >= stop;
               state := done;
         δ :   exit(0);
      end;
    
    begin
       var ρ := range_struct.λ(1,20,2);
       var π := range_struct.λ(2,20,2);
    
       for var i in [1..5] do begin
           writeln('a   ',i:2, 'th call : ', ρ.resume);
           writeln('  b ',i:2, 'th call : ', π.resume);
       end;
    
       writeln;
       writeln('Mwah ha ha. GOTO is back, baby!');
       readln;
    end.

    That wasn't the original function-calling code that I had posted.
    It was only after a bit of mental digestion that I realised that this is (in my opinion) a really good, simplified, understandable model of a coroutine.

     

    ... and soon after I found out the original C code was part of an article commenting on c++ coroutines https://probablydance.com/2021/10/31/c-coroutines-do-not-spark-joy/

     


  2. It is, in a sense, a bit sad that the lack of user-definable infix operators leads to this sort of "creative  interpretation".

    Shouldn't we complain about this almost as much as about 'With' eg ?

    This could have (should have?) been implemented by a JoinTo( s1, s2 : string) function.

     

    An actual Equality Operator should exhibit the qualities of being reflexive (a=a), symmetric (a=b => b=a), and transitive (a=b, b=c => a=c)

     

    (and going back to the first question ... it wouldn't need to return a boolean.  Eg some cases, like a partial order, it might return an optional boolean)


  3. @Ali Dehban I like the idea of the area you were investigating here.  
    Stretching  my brain to a function that can return any type is something I have thought about in the last year or so.

     

    It took a while before I started to wrap my head around enough it to have questions, 'tho.

     

    The first was that   

      IMyInterface<T> = interface
          function DoSomething: T;
        end;

    looks a lot like the definition of an anonymous function ... and then I replaced it with records.  But I'll skip that for now.

     

    My second eventual question is why have

    function UseInterface<T> ( obj: IMyInterface<T> ) : T;
    begin
      Result := obj.DoSomething;
    end;

     vs just ?

    obj.DoSomething;
    

    So, as a first step I ended up with the following :  (I realise you would have been simplifying the code above from your real use case)

    {$APPTYPE CONSOLE}
    program Project3;
    uses
        System.SysUtils, System.Variants, System.Classes, System.Rtti;
    
    type
        IWithAny<T> = interface
            function DoSomething: T;
        end;
    
        TWithAny<T> = class(TInterfacedObject, IWithAny<T>)
            function DoSomething : T;
        end;
    
    { TWithAny<T> }
    function TWithAny<T>.DoSomething: T;
    begin
        var val: TValue := TValue.Empty;
        var typ: T      := default(T);
    
        case GetTypeKind(T) of
            tkString,
            tkUString  :  val := 'Hello';
            tkInteger  :  val := 20;
            tkClass    :  if TValue.From<T>(typ).IsType<TStringList> then
                          begin
                             val := TStringList.Create;
                             val.AsType<TStringList>.Add('Hello from StringList');
                          end;
        end;
        Result := Val.AsType<T>;
    end;
    
    var
        obj1 :  IWithAny< Integer >;
        obj2 :  IWithAny< String >;
        obj3 :  IWithAny< TStringList >;
    begin
        obj1 := TWithAny< Integer >    .Create;
        obj2 := TWithAny< String >     .Create;
        obj3 := TWithAny< TStringList >.Create;
    
        writeln( obj1.DoSomething );
        writeln( obj2.DoSomething );
    
        var lvstr := obj3.DoSomething;
        writeln(lvstr.Text);
        lvstr.Free;
    
        readln;
        ReportMemoryLeaksOnShutdown := True;
    end.

    What I'd kinda like to see is the interface return a (managed record?) holding the <T>, made to clean up it's own memory.

    • Like 1

  4. I have no problem with pointer to record ... I have been pretty scathing about C-style function declarations that are not broken down into more readable sub-types.

     

    We could separate ownership from access with something like :

    begin
      var o := TRecObject.Create;
          begin
             var p:PRec := @o.r;
             // use p for whatever
          end
      o.Free;
    end

    That pointer could be copied, passed to functions, whatever ... and simply pass out of scope.

    It wouldn't have ownership of the data object, and has no responsibility to release it.

     

    [edit - forgot] Or of course, you can have methods in the class to control and implement the access to the data object.

     

    I think this also highlights that the outer wrapper can be something other than manually managed.

     


     

     


  5.  

    Yes. 100% I'm not arguing about it requiring management as is, ... or added structure to automate that.

     

    But of the code below, the new / dispose is (imo) ugly code that is unsuited to 2023, and to the long-term goal of regaining wider recognition of Delphi as a modern and relevant language.

     

    My standard rant / pedestal is that over the coming years we need to see Delphi improve it's language .. and it's practice ... to not be left behind by the general progress of other languages.

    Currently Delphi doesn't really qualify as a good teaching language any more - which I think is really sad.

    Without some more modern language facilities, it would be unfair to modern (say university level) students, and I'd like to see that change. 

     

    begin
      var p:PRec := New(p);
      try
        p.x := 5;
      finally
        Dispose(p);
      end;
    end;
    
    begin
      var o := TRecObject.Create;
      try
        o.r.x := 5;
      finally
        o.Free;
      end;
    end

  6. 4 minutes ago, Dalija Prasnikar said:

    From the safety perspective typed pointers in Delphi are equally safe or unsafe as classes. You still have to manage their memory and pay attention to what you are doing with them.  

    Yes. I was arguing from a consistency and code-style view point.

     

    Quote

    Typed pointers are quite different from raw pointers. 

    I don't agree. I don't think it is the generally accepted meaning of 'raw pointer'. 
    Maybe you are thinking of 'void pointer' ?
     

    >> A raw pointer is a pointer whose lifetime isn't controlled by an encapsulating object, such as a smart pointer. A raw pointer can be assigned the address of another non-pointer variable, or it can be assigned a value of nullptr.

    Microsoft Learn - Raw Pointers


  7. Hi Ian.

    A nullable or option type basically adds a single possible "uninhabited" state to a return type of any kind.

    You can think of a function that can throw an exception in almost exactly the same way ... it has two possible states ... the normal return or the exception.

     

    I was operating on the assumption that the boolean you returned from your original function was indicating whether you successfully created a date range.


  8. Not to be negatively critical, but can I suggest that there's a discussion to be had as to whether out parameters should be an out-dated usage style.

     

     

    @Ian Branch Can I take it that what you want returned from the function is EITHER a failure condition/error result OR a tuple of (startdate, enddate) ?

     

    If so, this is really a function with a single return type ...

     

    function GetWeekDates(const GivenDate: TDateTime; var SOWDay: string = 'SU') -> FAIL | (startdate, enddate) 

    This could be known as a Nullable type, or Option type ... in Rust it is the Result type.


    When you return from this function, you either have (effectively) no result, or an inhabited tuple.

     

    There are quite a few published examples in Delphi of Nullable types .. .from Allen Bauer through to Spring4D.  I'm not sure all of them capture the idea in the most functional way.

     


  9. 4 hours ago, David Heffernan said:

    How is this materially different? You still need to explicitly Free, and you need try/finally. The class is just extra baggage. For what gain?  

    I would say, in probably every language, the recommendation should be to code in a way that is familiar and idiomatic for that language.

     

    If we wanted stack-allocation and hands-off memory safety, then we can use records .. even custom managed records.

    But if we want to use heap memory, then I think it only makes sense to do it in the same idiomatic language as all our other Delphi code.

     

    I don't think it's a wild opinion in 2023 that pretty much nobody outside of C code should be cooking up raw pointers.

    At least C++ has unique pointer and shared pointer. 
    I guess we could cook up a smart pointer record to look after the heap-allocated memory ... that would still be avoiding raw pointers.

    In summary, I think the lesson from the C++ ecosystem is: pointers can be ok ... but {owning, raw} pointers are something to avoid.


  10. Wouldn't this be better expressed as a class containing a record?

    type
    TMyRec = record ... end;
    
    TMyRecC = class
        r : TMyRec;
    end;

    If C++ can declare "no raw pointers", then we certainly should avoid it in Delphi, right?
    This way you can maintain the usual convention with TMyRecC.Create and .Free, have constructor & destructor ... or use an interface ... or create your own smart pointer class ...


  11. Fibers under the magnifying glass
    Gor Nishanov (gorn@microsoft.com) - author? of c++ coroutines
    (Advocating coroutines over fibers.)
    Abstract
    "Fibers (sometimes called stackful coroutines or user mode cooperatively scheduled threads) and stackless coroutines (compiler synthesized state machines) represent two distinct programming facilities with vast performance and functionality differences. This paper highlights efficiency, scalability and usability problems of fibers and reaches the conclusion that they are not an appropriate solution for writing scalable concurrent software."
    https://open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1364r0.pdf

    • Like 1

  12. Neater and equally fast ...

    type
      Conts = (edit, button, check, form, frame, listbox, pgctrl, tabctrl, radiobtn, combobox);
      Ctest = set of Conts;
    
      const
      Strs : array[Conts] of string =
                           (    'Edit',       'Button',      'Check',     'Form',      'Frame',
                             'ListBox',  'PageControl', 'TabControl', 'RadioBtn',   'ComboBox'   );
    
    function IsIN(aID: Conts): boolean ;     // inline;
    begin
      const test : Ctest = [button, form, listbox, tabctrl, combobox];
      IsIn := aId in test;
    end;

    image.thumb.png.7f3632614a24cfb56b672ffa7b440145.png

     

     

     

    • Thanks 1

  13. On 3/22/2022 at 1:32 AM, Stefan Glienke said:

    Depending on the number of IDs you have it might be worth using power of two and bitmasks or an enum directly because that would only require one cmp/test making the function twice as fast and perfect for inlining which would then also eliminate the function call overhead at all. 

    As Stefan said ... without const ...

    procedure AssignControlsRec;
    begin
      xControlsRec.EditID        :=   1;    xControlsRec.EditStr        := 'Edit'       ;
      xControlsRec.ButtonID      :=   2;    xControlsRec.ButtonStr      := 'Button'     ;
      xControlsRec.CheckID       :=   4;    xControlsRec.CheckStr       := 'Check'      ;
      xControlsRec.FormID        :=   8;    xControlsRec.FormStr        := 'Form'       ;
      xControlsRec.FrameID       :=  16;    xControlsRec.FrameStr       := 'Frame'      ;
      xControlsRec.ListBoxID     :=  32;    xControlsRec.ListBoxStr     := 'ListBox'    ;
      xControlsRec.PageControlID :=  64;    xControlsRec.PageControlStr := 'PageControl';
      xControlsRec.TabControlID  := 128;    xControlsRec.TabControlStr  := 'TabControl' ;
      xControlsRec.RadioBtnID    := 256;    xControlsRec.RadioBtnStr    := 'RadioBtn'   ;
      xControlsRec.ComboBoxID    := 512;    xControlsRec.ComboBoxStr    := 'ComboBox'   ;
    end;
    
    function IsIN(aID: integer): boolean ;     // inline;
    begin
      const test = xControlsRec.ButtonID  + xControlsRec.FormID + xControlsRec.ListBoxID + xControlsRec.TabControlID + xControlsRec.ComboBoxID;
      IsIn := (test and aID) <> 0;
    end;

    image.thumb.png.600782fb8f5e55c7f08106c44c2e3f97.png

     

    image.thumb.png.d5c2f762fabb5f0d73a96b4112277038.png

    • Thanks 1
×