Home > Delphi, Object Pascal > Lazy params, why not?

Lazy params, why not?

Lazy management of child objects is something we normally dont tolerate in object pascal, and pupils caught writing code like this under my supervision would be promptly court marshaled and shot after a damn good thrashing. Ok maybe not thrashing, and probably not shooting either. Come to think of it, a faint tickle or determined gaze should do the trick.

Silly jokes aside, “Lazy management” is my take on creating objects which are explicitly designed to live for short periods of time. Typically just enough for you to extract some value, format something or just check a state. For values of a more permanent state – especially values you would check regularly, lazy management would be a waste.

Practical example

Sometimes its good to let the stack sort itself

Sometimes its good to let the stack sort itself

In my QTX research and development IDE, I have something called “file sources”. In short it’s a class system for completely abstracting you from the filesystem. Any filesystem to be exact. It’s very clever and it allows the entire IDE to be utterly agnostic regarding where files originate, because the closest you get to files are through reference identifiers.

Another side of this system is support for packages, namely zip based storage containers. The cool part is that the IDE doesnt distinguish between a folder on disk and files coming from inside a package. Why should it? It pertains completely to the global file repository, handling files through references and as such — the files can come from your cloud account (spoiler alert), an FTP account or anywhere else for all the IDE knows.

Naturally parameters to initialize a filesource cant be easily streamlined, so I have opted for a string based parameter function. The Open() method takes a semi-colon delimited name/value pair string, creates a TStringList to break the string down, then I stuff the name/value pairs into a dictionary for quick access later.

So initializing the parameter parser is super easy, like this:

FParams:=TSourceParameters.Create(‘name=testing;color=12;number=94.5;gone=true’);

Since the string is parsed and everything placed in a dictionary, it allows me to extract values quickly by name. But — I really dont want to clutter my code with datatype checks for each and every one! I want a clean interface, a bit like TCustomDataset’s TParams. But at the same time, I dont want to cache any value objects because the values will only exist for a short period of time.

I want access which is simple, like this:

caption:=FParams.Get(‘name’).AsString;

TInterfacedObject makes sense

I must admit, I rarely use TInterfacedObject out of the box. And when I do, I tend to disable the default reference counting because I may need interfaces – but I rarely need reference counting (happily the COM days are almost behind us). But in this particular case, the default behavior is excellent.

In the above one-liner snippet, the Get() method returns an object, but you may notice that I have no “free” statement involved. So at first glance you will probably think that the TSourceParameters object cache’s the objects in a TObjectList or something like that. Well I dont, we just create an object on the fly and forget about it.

So here goes:

type

  TSourceParameters = Class(TObject)
  private
    FLUT: TDictionary<string,string>;
  public
    type
    TSourceParameter = Class(TInterfacedObject)
    private
      FData:      String;
    public
      function    Empty:Boolean;
      function    AsString:String;
      function    AsInteger:Integer;
      function    AsBool:Boolean;
      function    AsFloat:Double;
      Constructor Create(Data:String);virtual;
    end;

    function    Get(Name:String):TSourceParameter;
    Constructor Create(CommandText:String);virtual;
  end;

//#############################################################################
// TSourceParameters.TSourceParameter
//#############################################################################

Constructor TSourceParameters.TSourceParameter.Create(Data:String);
begin
  inherited Create;
  FData:=trim(Data);
end;      

function TSourceParameters.TSourceParameter.Empty:Boolean;
begin
  result:=length(FData)<1;
end;      

function TSourceParameters.TSourceParameter.AsString:String;
begin
  result:=FData;
end;

function TSourceParameters.TSourceParameter.AsInteger:Integer;
begin
  TryStrToInt(FData,Result);
end;

function TSourceParameters.TSourceParameter.AsBool:Boolean;
begin
  TryStrToBool(FData,Result);
end;

function TSourceParameters.TSourceParameter.AsFloat:Double;
begin
  TryStrToFloat(FData,Result);
end;

//#############################################################################
// TSourceParameters
//#############################################################################

Constructor TSourceParameters.Create(CommandText:String);
var
  mList:  TStringlist;
  x:      Integer;
  mId:    String;
begin
  inherited Create;
  FLUT:=TDictionary<string,string>.Create;

  commandText:=trim(commandText);
  if length(commandText)>0 then
  Begin
    (* Populate our lookup table *)
    mlist:=TStringList.Create;
    try
      mList.Text:=StringReplace(CommandText,';',#13,[rfReplaceAll]);
      for x:=0 to mList.Count-1 do
      begin
        mId:=lowercase(trim(mList.Names[x]));
        FLut.AddOrSetValue(mId, trim(mList.ValueFromIndex[x]) );
      end;
    finally
      mList.Free;
    end;
  end;
end;

function TSourceParameters.Get(Name:String):TSourceParameter;
var
  mData:  String;
begin
  FLut.TryGetValue(name,mData);
  result:=TSourceParameter.Create(mData);
end;

Simple, easy to use, straight to the point. And we let Delphi deal with releasing any TSourceParameter instances.
So using this would be a case of:

procedure TForm1.Button1Click(Sender: TObject);
var
  FParams: TSourceParameters;
  mText:  String;
  mFloat: Double;
  mBool:  Boolean;
  mInt:   Integer;
begin
  FParams:=TSourceParameters.Create('name=testing;color=12;number=94.5;gone=true');
  try
    mtext:=FParams.Get('name').AsString;
    mInt:=FParams.Get('color').AsInteger;
    mFloat:=FParams.Get('number').AsFloat;
    mBool:=FParams.Get('gone').AsBool;
  finally
    FParams.Free;
  end;
end;

But again, using this technique is not suitable for objects you intend to read over-and-over. For that kind of data you are better off using a proper TObjectList.

Advertisements
  1. May 25, 2015 at 6:39 pm

    No need for StringReplace. Use the TStrings properties DelimitedText, Delimiter and StrictDelimiter instead.

    Also I suspect it’s more efficient to use the SplitString() function instead of a string list.

    • Jon Lennart Aasenden
      May 25, 2015 at 9:19 pm

      jajajajaja… I spent a whole 2 minutes on it 🙂 I actually thought about using the delimiter thing but stringreplace just.. well it IS called lazy so 😀

  2. May 25, 2015 at 7:03 pm

    Sounds close to our TDocVariant custom variant type.
    http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_80
    With much more advantages, like late-binding for a very readable code, without the need to call Free.

    var d: variant;

    d := _Json(‘name=”testing”;color=12;number=94.5;gone=true’);
    mtext:=d.name;
    mInt:=d.color;
    mFloat:=d.number;
    mBool:=d.gone;

    • Jon Lennart Aasenden
      May 25, 2015 at 9:20 pm

      Yeah not exactly top of the line code this, more to demonstrate that sometimes you can just spawn off some instances and forget them — if the situation calls for it.

  3. May 25, 2015 at 9:28 pm

    Why can’t TSourceParameter be a record, then it will self-destruct without needing to go past the heap?

  4. May 26, 2015 at 6:30 am

    AFAIR you can use lazy parameters in IDispatch interface.
    See http://docwiki.embarcadero.com/RADStudio/XE8/en/Automation_Objects_%28Win32_Only%29
    The language may be extended to support such name parameters for optional parameters:
    procedure Lazy(const name: string=’testing’; color: integer=12;
    number: double=94.5; gone: boolean=true);

  5. Jon Lennart Aasenden
    May 26, 2015 at 6:44 am

    Thanks for all the feedback guys. A lot of good ideas!
    First, regarding the use of a full object rather than a record: In this particular case a record would do fine. I was more trying to demonstrate an idea than perfection of code, hence I picked the object. An object gives us more freedom to implement sub-systems even on the ad-hoc object level.

    I use the above technique a lot under SMS, where the JS garbage collector takes care of everything — so when turning attributes of a tag into an object path (obj1->obj2->method) I can just spin-off instances giving them just enough information to find their tag-target.

    Lately I’ve started to do a lot of the same under Delphi, in the above form. and indeed – in many cases a record would more than suffice.

    As for the stringlist, well I quite frankly had my mind elsewhere, so the delimiterchar would be a better option yes 🙂

    And third, the use of a stringlist all together: As my software becomes ever more complex and large, I’ve started to appreciate the use of pre-fabricated object over raw-metal speed. I was tempted to parse the string manually there and then, or use some SplitStr() scheme — but for me at this point in time, readability and easy maintenance means more than brute force.

    It’s always easy to go back and optimize code like this at a later date.

    Actually noticed I forgot to add the destructor, so it would leak memory like mad 🙂

  6. May 26, 2015 at 7:07 pm

    Did you actually test this? It does not work; the TSourceParameter objects are not freed automatically.
    Deriving from TInterfacedObject alone does not enable reference counting for the derived class. You still have to implement an interface (at least IInterface or IUnknown) and assigne the created object to a variable of an appropriate interface type.

    • Jon Lennart Aasenden
      August 23, 2016 at 3:08 pm

      Like i said in the last line of my comment above: “Actually noticed I forgot to add the destructor, so it would leak memory like mad:)”, so yes I did notice that flaw.

      It was just a “scetch” of a technique, not a code standard 🙂

      • August 24, 2016 at 5:26 am

        No, it’s not a matter of the missing destructor. It leaks memory, because TSourceParameters.TSourceParameter does not implement an interface and the created objects are not assigned to interface variables. Thus no reference counting is done and no TSourceParameter object is freed automatically.
        There actually is no need for a destructor in TSourceParameter, because the only data member is a string, which is already ref-counted.

        • Jon Lennart Aasenden
          August 29, 2016 at 3:17 pm

          The point was that people have to do that themselves. When scetching out a painting you dont deliver a finished paiting. Its a scetch, a concept, an idea.
          I dont expect people to just have a string in the object. This is not an example to study “as a whole”, but a concept that can be implemented on a grander scale.

  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: