Skip to content

Commit

Permalink
Variant records and related concepts
Browse files Browse the repository at this point in the history
  • Loading branch information
michaliskambi committed Jul 30, 2024
1 parent 17e9337 commit d75359a
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ modern_pascal_introduction_ukrainian.xml
/code-samples*/*.o
/code-samples*/*.exe
/code-samples*/anonymous_functions
/code-samples*/array_of_const
/code-samples*/anon_functions_list_map_foreach
/code-samples*/anon_functions_assignment_test
/code-samples*/callbacks
Expand Down Expand Up @@ -56,6 +57,8 @@ modern_pascal_introduction_ukrainian.xml
/code-samples*/persistent
/code-samples*/records
/code-samples*/static_class_method
/code-samples*/variant_in_record
/code-samples*/variant_types
/code-samples*/with_virtual_methods
/code-samples*/without_virtual_methods
/code-samples*/method_with_self_nil
Expand Down
43 changes: 43 additions & 0 deletions code-samples/array_of_const.dpr
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}

uses SysUtils;

{ Example function that concatenates all elements of an array of const
into a String. }
function GlueEverything(const MyArray: array of const): String;
var
I: Integer;
begin
Result := '';

for I := 0 to High(MyArray) do
begin
// treat MyArray[I] as TVarRec, check for type and do something
case MyArray[I].VType of
vtInteger:
begin
Writeln('Integer: ', MyArray[I].VInteger);
Result := Result + IntToStr(MyArray[I].VInteger) + ' ';
end;
vtAnsiString:
begin
Writeln('Ansi String (8-bit chars): ', AnsiString(MyArray[I].VAnsiString));
Result := Result + AnsiString(MyArray[I].VAnsiString) + ' ';
end;
vtUnicodeString:
begin
Writeln('Unicode String (16-bit chars): ', UnicodeString(MyArray[I].VUnicodeString));
Result := Result + UnicodeString(MyArray[I].VUnicodeString) + ' ';
end;
else
Writeln('Something else, ignoring');
end;
end;
end;

var
S: String;
begin
S := GlueEverything([123, 'Hello', 'World', 456]);
Writeln(S);
end.
40 changes: 40 additions & 0 deletions code-samples/variant_in_record.dpr
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}

type
TVector2 = packed record
case Integer of
0: (X, Y: Single);
1: (Data: array [0..1] of Single);
end;

TVector3 = packed record
case Integer of
0: (X, Y, Z: Single);
1: (Data: array [0..2] of Single);
2: (XY: TVector2);
end;


var
V2: TVector2;
V: TVector3;
I: Integer;
begin
Writeln('Size of TVector2 is ', SizeOf(TVector2));
Writeln(' Should be equal to ', SizeOf(Single) * 2);

Writeln('Size of TVector3 is ', SizeOf(TVector3));
Writeln(' Should be equal to ', SizeOf(Single) * 3);

V.X := 1;
V.Y := 2;
V.Z := 3;

for I := 0 to 2 do
Writeln('V.Data[', I, '] is ', V.Data[I]:1:2);

V2 := V.XY;

for I := 0 to 1 do
Writeln('V2.Data[', I, '] is ', V2.Data[I]:1:2);
end.
12 changes: 12 additions & 0 deletions code-samples/variant_types.dpr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{$ifdef FPC} {$mode objfpc}{$H+}{$J-} {$endif}

uses Variants;
var
V1, V2, V3: Variant;
begin
V1 := 'My String';
V1 := 123; // V1 no longer holds String, it has Integer now
V2 := 456.789;
V3 := V1 + V2; // result is float
Writeln('V3 = ', V3);
end.
49 changes: 49 additions & 0 deletions modern_pascal_introduction.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2083,6 +2083,55 @@ But records are still very useful when you need speed or a predictable memory la
** to make dirty low-level tricks (like unsafe typecasting one type to another, being aware of their memory representation).
* Records can also have `case` parts, which work like _unions_ in C-like languages. They allows to treat the same memory piece as a different type, depending on your needs. As such, this allows for greater memory efficiency in some cases. And it allows for more _dirty, low-level unsafe tricks_:)

### Variant records and related concepts

The concept _variant_ may refer to 3 distinct (though, deep down related) things in Pascal:

#### Variant records

_Variant records_ allow to define a section at the end of your record where the same memory can be accessed by a few different names/types.

This is described on https://en.wikipedia.org/wiki/Tagged_union on Wikipedia. _"Union"_ is more common name for this in other languages. See also https://www.freepascal.org/docs-html/ref/refsu15.html .

Example:

[source,pascal]
----
include::code-samples/variant_in_record.dpr[]
----

#### Variant type

`Variant` is a special type in Pascal that underneath can hold values of various types. Moreover, operators are defined to allow operating on them and converting their values at run-time.

The effect is a bit similar to scripting programming languages with dynamic typing.

Do not use them without consideration: things are a bit less safe (you don't control types, conversions happen implicitly). Also there's a small performance hit, since all operations need to check and synchronize the types at run-time.

But sometimes it is makes sense. Namely, when you have to process data that intrinsically indeed may have different types, and you only know those types at runtime. E.g. when you want to process result of SQL `select * from sometable` in a generic database viewer (not knowing table structure at compile-time).

[source,pascal]
----
include::code-samples/variant_types.dpr[]
----

NOTE: Technically, `Variant` is realized using `TVarData` internal type, which is a record with variants. So these concepts are connected. But you should *not need to know this*, you should not use `TVarData` explicitly.

#### TVarRec in array of const

When you use `array of const` special parameter type, it is passed as an array of `TVarRec`. See

- `TVarRec` in FPC: https://www.freepascal.org/docs-html/rtl/system/tvarrec.html

- `TVarRec` in Delphi: https://docwiki.embarcadero.com/Libraries/Sydney/en/System.TVarRec

This is useful to pass to a routine parameters of arbitrary (not known at compile-time) types. For example, to implement routines like standard `Format` (similar to `sprintf` in C) or _Castle Game Game_ `WriteLnLog` / `WriteLnWarning`.

[source,pascal]
----
include::code-samples/array_of_const.dpr[]
----

### Old-style objects

In the old days, Turbo Pascal introduced another syntax for class-like functionality, using the `object` keyword. It's somewhat of a blend between the concept of a `record` and a modern `class`.
Expand Down

0 comments on commit d75359a

Please sign in to comment.