diff --git a/.gitignore b/.gitignore index ba1e57e..7f59605 100644 --- a/.gitignore +++ b/.gitignore @@ -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 @@ -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 diff --git a/code-samples/array_of_const.dpr b/code-samples/array_of_const.dpr new file mode 100644 index 0000000..ef30158 --- /dev/null +++ b/code-samples/array_of_const.dpr @@ -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. diff --git a/code-samples/variant_in_record.dpr b/code-samples/variant_in_record.dpr new file mode 100644 index 0000000..7c2238d --- /dev/null +++ b/code-samples/variant_in_record.dpr @@ -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. \ No newline at end of file diff --git a/code-samples/variant_types.dpr b/code-samples/variant_types.dpr new file mode 100644 index 0000000..5d08f7b --- /dev/null +++ b/code-samples/variant_types.dpr @@ -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. diff --git a/modern_pascal_introduction.adoc b/modern_pascal_introduction.adoc index 0d8128f..a026c44 100644 --- a/modern_pascal_introduction.adoc +++ b/modern_pascal_introduction.adoc @@ -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`.