cli  
Article Series

This article series discuss more than 30 different programming languages. Please read overview before you read any of the details.

Playing with Records Related Articles.

Where to Discuss?

Local Group

Preface

Goal: An object oriented approach to collect unique record fields using Free Pascal.

This idea of article section is simply to utilize TList, or to be exact, we are going to use the lighter TFPList.

Classes

In order to do this, We are going to use three more classes, And one additional helper unit:

  1. TSongListBase class, in MySongListBase unit.

  2. TSongList2 class, in MySongList2 unit (using array)

  3. TSongList3 class, in MySongList3 unit (using TStringList).

  4. ImportMySongs procedure, in MySongHelper unit.


9: Base Class

The base class is rather text book. And actually, I copy paste most part, from official documentation.

Using TFPList

The TFPList hold list of object. We require to define the object first, such as song record. And we also setup the pointer type.

type
  PSong=^TSong;

  TSong = record
    ...
  end;

This way we use list instead of array.

MySongListBase Interface

The code can be shown as below:

{$mode objfpc}{$H+}
unit MySongListBase;

interface
uses Classes, SysUtils;

type
  TTags = array of string;

  PSong=^TSong;

  TSong = record
    Title : string;
    Tags  : TTags;
  end;

Pascal: MySongListBase: Interface

TSongListBase Class

The object pascal part is always a fun to write. I mean I can get the overview clearly in Pascal. Get the structured well in interface. And bother with implementation later.

Using the T prefix is not mandatory. It might comes from the Turbo Pascal era. But I rather call it the. So now we have the song list base class, as below.

type
  TSongListBase = class(TFPList)
  protected
    function Get(Index: integer): PSong;
  public
    destructor Destroy; override;
    function Add(ASong : PSong): integer;
    property Items[Index: integer]: PSong read Get; default;
  end;

Pascal: MySongListBase: TSongListBase Class

MySongListBase Implementation

The implementation is, of course longer than the interface.

implementation

function TSongListBase.Get(Index: integer): PSong;
begin
  Result := PSong(inherited Get(Index));
end;

function TSongListBase.Add(ASong: PSong): integer;
begin
  Result := inherited Add(ASong);
end;

And do not forget the Destructor.

destructor TSongListBase.Destroy;
var I : integer;
begin
  for I := 0 to Count-1 do begin
    Dispose(Items[I]);
  end;
  inherited Destroy;
end;

end.

Pascal: MySongListBase: Implementation

Using MySongListBase

What good is it of making a class, without ever use it? Here we are with the example.

Declare everything as usual, we need the source MySongs array as well, just to fill the object.

uses MySongListBase, MySongs;

var
  TS: TSongListBase;
  ASong : PSong;
  Song : MySongs.TSong;

Create the TSongListBase object, and fill the list.

begin
  TS := TSongListBase.create;
  for Song in Songs do begin
    ASong  := new(PSong);
    ASong^ := MySongListBase.TSong(Song);
    TS.Add(ASong);
  end;

  WriteLn(TS[2]^.Title);

  TS.Free;
end.

With the result similar as below:

❯ ./21-tfplist-a
Knockin' on Heaven's Door

Pascal: MySongListBase: Using The Unit


10: Helper Unit

Since we are going to fill the object, multiple times in different code, it is a good idea to separate it, into its own unit.

MySongsHelper Interface

The code can be shown as below:

unit MySongsHelper;

interface

uses MySongListBase, MySongs;

procedure ImportMySongs(
  Songs: array of MySongs.TSong;
  TS: TSongListBase);

Pascal: MySongsHelper: Interface

MySongsHelper Implementation

While the ImportMySongs lies in the implementation part.

implementation

procedure ImportMySongs(...);
var ...
begin ... end;

end.

With the complete procedure as below:

procedure ImportMySongs(
  Songs: array of MySongs.TSong;
  TS: TSongListBase);
var
  ASong : PSong;
  Song : MySongs.TSong;
begin
  for Song in Songs do
  begin
    ASong  := new(PSong);
    ASong^ := MySongListBase.TSong(Song);
    TS.Add(ASong);
  end;
end;

Pascal: MySongsHelper: Implementation

Using MySongsHelper

Now our code can be shorter.

Do not forget to include our helper unit.

uses MySongListBase, MySongs, MySongsHelper;

Then just use it in main program entry point.

var TS: TSongListBase;
begin
  TS := TSongListBase.create;
  ImportMySongs(MySongs.Songs, TS);
  WriteLn(TS[2]^.Title);
  TS.Free;
end.

With the result exactly the same as previous example:

❯ ./21-tfplist-b
Knockin' on Heaven's Door

Pascal: MySongListBase: Using The Unit


11: Descendant Class: Using Array

We have two approach, using either array and TStringList. Consider start with array.

MySongList2 Interface

The idea is just make a child class.

Note that this is just an example. You can gather base class and child class at once, put all the method and property in just one class, instead of separated class.

{$mode objfpc}
unit MySongList2;

interface
uses Classes, SysUtils, MySongListBase;

type
  TSongList2 = class(TSongListBase)
  private ...
  public  ...
  end;

TSongList2 Class

The overview of song list child class is, as below:

type
  TSongList2 = class(TSongListBase)
  private
    function GetTags(Index: integer): TTags;
  public
    function GetAllTags(): TTags;
    function GetUniqueTags(): TTags;
    property ItemsTags[Index: integer]: TTags read GetTags;
  end;

Pascal: MySongList2: Interface

MySongList2 Implementation

The implementation contains three methods.

function TSongList2.GetTags(Index: integer): TTags;
begin
  Result := Get(Index)^.Tags;
end;

function TSongList2.GetAllTags(): TTags;
var ASong : PSong;
begin
  Result := [];
  for ASong in Self do
    System.Insert(
      ASong^.Tags, Result, High(Result)+1);
end;

Pascal: MySongList2: Implementation

The last method, get the unique tags, using previous GetAllTags method.

function TSongList2.GetUniqueTags(): TTags;
var 
  Unique: TTags = ();
  Tag, Uni : string;
  Exist : boolean;
begin
  for Tag in GetAllTags() do
  begin
    Exist := false;
    for Uni in Unique do
      if (Tag = Uni) then Exist := true;
    if (not Exist) then
      System.Insert(Tag, Unique, High(Unique)+1);
  end;

  Result := Unique;
end;

Pascal: MySongList2: Implementation

Using MySongList2

Consider to see the object in action.

Declare everything as usual, pay attention to the uses clause. This take longer, required a few dependencies.

{$mode objFPC}

uses 
  SysUtils, MySongs, MySongsHelper,
  MySongListBase, MySongList2;

var
  TS: TSongList2;
  SA : TStringArray;

Pascal: MySongList2: Using The Unit: Header

Create the TSongList2 object, fill the list, and test every method.

begin
  TS := TSongList2.create;
  ImportMySongs(Songs, TS);

  WriteLn(TS[2]^.Title);
  writeLn(TS.ItemsTags[1][0]);

  SA := TS.GetAllTags();
  WriteLn(ANSIString.Join(', ', SA));

  WriteLn(ANSIString.Join(
    ', ', TS.GetUniqueTags));

  TS.Free;
end.

The result would be similar as below:

❯ ./22-tfplist
Knockin' on Heaven's Door
60s
60s, jazz, 60s, rock, 70s, rock, 70s, pop
60s, jazz, rock, 70s, pop

Pascal: MySongList2: Using The Unit: Main


12: Descendant Class: TStringList

Consider the second approach, using TStringList instead of array.

MySongList3 Interface

TSongList3 Class

This should be simpler, only consist of one method. The overview of song list class child is, as below:

{$mode objfpc}
unit MySongList3;

interface
uses Classes, SysUtils, MySongListBase;

type
  TSongList3 = class(TSongListBase)
  public
    function GetUniqueTags(): TTags;
  end;

Pascal: MySongList3: Interface

MySongList3 Implementation

The implementation only consist one method.

function TSongList3.GetUniqueTags(): TTags;
var
  SL    : TStringList;
  ASong : PSong;
  Tag   : ANSIString;
begin
  SL := TStringList.Create;
  SL.Duplicates := dupIgnore;
  SL.Sorted := true;

  for ASong in Self do
    for Tag in ASong^.Tags do
      SL.Add(Tag);

  Result := SL.ToStringArray(0, Count-1);

  SL.Free;
end;

Pascal: MySongList3: Implementation

This should be self explanatory.

Using MySongList3

Have a look at the object in action.

Declare everything as usual, pay attention to the uses clause. This take longer, required a few dependencies.

{$mode objFPC}

uses 
  SysUtils, MySongs, MySongsHelper,
  MySongListBase, MySongList3;

var
  TS: TSongList3;

Create the TSongList3 object, fill the list, and the only one method.

begin
  TS := TSongList3.create;
  ImportMySongs(Songs, TS);

  WriteLn(ANSIString.Join(
    ', ', TS.GetUniqueTags));

  TS.Free;
end.

The result would be similar as below:

❯ ./23-tfplist
60s, 70s, jazz, pop, rock

Pascal: MySongList3: Using The Unit

Now your code is clean. Much shorter.


What is Next 🤔?

We are going to continue to concurrency in Free Pascal.

Consider continue reading [ Pascal - Playing with Records - Part Five ].