Is it safe to replace array of XXX with TArray<XXX> - arrays

I have quite a few variables declared as
var
Something: array of XXX;
begin
SetLength(Something, 10);
try
...
finally
SetLength(Something, 0);
end;
end;
To what extend is safe to have them replaced:
var
Something: TArray<XXX>;
begin
SetLength(Something, 10);
try
...
finally
SetLength(Something, 0);
end;
end;

As already answered, TArray<XXX> is exactly like any other custom type defined as array of XXX. In fact, TArray<XXX> is a custom type defined as array of XXX.
That said, a custom type defined as array of XXX is not equivalent to array of XXX in the context of a procedure or function parameter. In procedure Foo(x: array of Integer), x is an open array parameter, which can accept any type of integer array. In contrast, procedure Foo(x: TArray<Integer>) takes an actual TArray<Integer> type only. You can see the difference when attempting to pass a fixed-size array, but also when attempting to pass a TDynIntegerArray (a different type, also defined as array of Integer).
So, for variables, sure, if you have array of XXX, change it to TArray<XXX> all you want. Just make sure you don't do a global search and replace.

It is perfectly safe to do this. The compiler will produce identical output.
I personally would, all other things being equal, recommend doing so. The use of the generic array TArray<T> gives you much more flexibility and better type compatibility.
Of course those benefits can only be seen with more realistic code that does some work. You most typically see benefits when using generic containers. But you might also see benefits when trying to build code using multiple different libraries.
The use of generic arrays allows easy type compatibility. Before generic arrays you would define an array type like this:
TIntArray = array of Integer;
If two libraries do this then you have incompatible types. If the libraries agree to use generic arrays then there will be compatibility.
To see this more clearly, consider this fragment:
type
TIntArray1 = array of Integer;
TIntArray2 = array of Integer;
....
var
arr1: TIntArray1;
arr2: TIntArray2;
....
arr1 := arr2;
This assignment is not valid and fails with a type mis-match compiler error. This is entirely to be expected within the Pascal language. It is after all strongly typed and these are distinct types. Even if they are implemented identically.
On the other hand:
var
arr1: TArray<Integer>;
arr2: TArray<Integer>;
....
arr1 := arr2;
is valid and does compile. The documentation for generic type compatibility says:
Two instantiated generics are considered assignment compatible if the base types are identical (or are aliases to a common type) and the type arguments are identical.

Related

Access Types for Stack Allocated Array in Ada

I'm trying to do something with array passing and access types. I've run into a situation where the stack size on an embedded system makes it difficult to pass around a large array through the typical parameter passing mechanism.
To save stack size, I've started using access types but I don't want to do dynamic allocation.
What I have is something like this:
type My_Array_Type is array (Natural range<>) of Integer;
type My_Array_Type_Ptr is access all My_Array_Type;
procedure Do_Stuff(Things : My_Array_Type_Ptr) is
begin
-- things
end Do_Stuff;
procedure Do_Stuff(Num_Things : Integer) is
Things : My_Array_Type_Ptr := new My_Array_Type(1..Num_Things);
begin
Do_Stuff(Things);
-- more things
end Do_Stuff;
HOWEVER, what I'd like to do is something like this:
type My_Array_Type is array (Natural range<>) of Integer;
procedure Do_Stuff(Things : access all My_Array_Type) is
begin
-- things
end Do_Stuff;
procedure Do_Stuff(Num_Things : Integer) is
Things : aliased My_Array_Type(1..Num_Things);
begin
Do_Stuff(Things'Access);
-- more things
end Do_Stuff;
But obviously it doesn't work.
Essentially, I want to pass a reference to the stack allocated array and change it in the other subprogram, but I don't want dynamically allocated memory. How can I do this?
Also, as a side note: I keep seeing conflicting information about if something is dynamically allocated if it has to be released or not -- I read most implementations don't have garbage collectors but don't need them. Can anyone clarify -- In the example I showed first, do I need to explicitly deallocate?
EDIT:
After trying the solutions mentioned below, I settled on a combination of two things:
Using the in out parameter passing mechanism.
Reducing the storage requirements of my data type.
So rather than Integer I'm using:
type UInt8 is new Interfaces.Unsigned_8;
Which is:
type UInt8 is mod 2**8
with Size => 8;
This works perfectly, since my values aren't actually integers, they are in fact unsigned bytes.
In Ada, you really don’t need to use access types for this situation.
Remarks below for native Ada code; for imported (& I suppose exported) subprograms the generated code obviously needs to obey the foreign language conventions.
The mode of a parameter (whether you’re allowed to write to it, whether (if you’re allowed to write to it) it has some initial value) is distinct from the parameter passing mechanism.
If you have a parameter of size larger than a register and the parameter passing mechanism is not by-reference (i.e. by passing the address of the actual object), complain to your compiler vendor!
The Ada way would be like this:
with Ada.Text_IO; use Ada.Text_IO;
with System.Storage_Elements;
procedure Jsinglet is
type My_Array_Type is array (Natural range<>) of Integer;
procedure Do_Stuff_1 (Things : in out My_Array_Type) is
begin
Put_Line (System.Storage_Elements.To_Integer (Things'Address)'Img);
end Do_Stuff_1;
procedure Do_Stuff (Num_Things : Integer) is
Things : My_Array_Type (1 .. Num_Things);
begin
Put_Line (System.Storage_Elements.To_Integer (Things'Address)'Img);
Do_Stuff_1 (Things);
end Do_Stuff;
begin
Do_Stuff (42);
end Jsinglet;
Running the program results here in
$ ./jsinglet
140732831549024
140732831549024
showing that the address has been passed, not the value.
The in out mode on Do_Stuff_1’s parameter means that Do_Stuff_1 can read the contents of the array passed to it before writing to it.
out would mean that Do_Stuff_1 shouldn’t read the contents until it has itself written them (it can, but - depending on the parameter’s type - it may read uninitialized or default-initialized data)
in would mean that the contents couldn’t be written to.
As of Ada2012 you can mark parameters as aliased and they will be passed by reference, but your source object must also be either tagged or aliased.
EDIT: It cannot be an array either it appears, so look below. Aliased parameters do work for types like integers, enumerations, records, etc. though.
You can also wrap the array in a tagged or limited record to force the compiler to use by reference passing as those are "by reference" types
type My_Array_Type is array (Natural range<>) of Integer;
type By_Reference(Length : Natural) is tagged record -- or limited
Elements : My_Array_Type(1..Length);
end record;
procedure Do_Stuff(Things : in out By_Reference) is
begin
-- things
end Do_Stuff;
procedure Do_Stuff(Num_Things : Integer) is
Things : By_Reference(Num_Things);
begin
Do_Stuff(Things);
-- more things
end Do_Stuff;
For your deallocation question, your example must explicitly deallocate the memory. There are ways to get automatic deallocation:
Compiler with Garbage Collection (I know of none)
Custom 3rd party library that provides it
Use a holder or container from Ada.Containers
Use a local (non library level) named access type with a specified storage size (your access type is library level and has not storage size specified). When the access type goes out of scope it will deallocate.

BigQuery Types: How to define a array of any type?

Bigquery SQL-UDFs are quite convenient, but is there a possibility to define array arguments without specifying the type of its elements? At least sometimes it would be nice to define operations on all arrays irrespective of the specific type. For example, one could create a function to get the most frequent elements of an array like this:
CREATE TEMPORARY FUNCTION anyHEAVY(arr Array<ANY TYPE>) AS ((
SELECT APPROX_TOP_COUNT(a, 1)[OFFSET(0)].value
FROM UNNEST(arr) as a
));
However, it seems like BQ expects here a specific type and the generic "ANY TYPE" placeholder does not work anymore. Up to now, I am just using the type "any type" without forcing the argument to be an array. This works, but is IMHO not really clean and would require an additional check. I can imagine, that any type would cause some troubles, especially, in case of nested arrays or structs. However, would be great if one could define functions of arrays containing only "elementary" types (excluding arrays and structs).
Currently there's no support for ARRAYS of ANY TYPE in SQL-UDF. The workaround is, as you mention, to declare the function as expecting ANY TYPE:
CREATE TEMPORARY FUNCTION anyHEAVY(arr ANY TYPE)

Which are differences between Variant arrays and dynamic arrays?

Which are differences between using a Variant array (Like shown here)
var
VarArray : Variant;
begin
VarArray := VarArrayCreate([0, 1], varInteger);
VarArray[0] := 123;
<...>
end;
instead of a common dynamic array?
var
DynArray : array of Integer;
begin
SetLength(DynArray, 1);
DynArray[0] := 123;
<...>
end;
Variants are a type that gets special handling from the compiler and the runtime. Under the hood, they are records of the type TVarRec. They can contain many different kinds of types internally, and can even be used to convert between some of these types. But they can also contain arrays of values, even arrays of other Variants, single- and multi-dimensional. That are Variant arrays. The System.Variants unit contains functions to define and handle such arrays.
Some more info on the Delphi Basics site.
Variants are typically used by Windows COM. Note that they can be pretty slow, especially Variant arrays with multiple dimensions. The number of types they can contain is limited.
Dynamic arrays are built-in types. They are normal arrays that can contain elements of any conceivable type, built-in or user defined. The difference with normal (static) arrays is that they can be instantiated, enlarged or shrunk dynamically (e.g. using SetLength), and their variables are pointers to the real array (which is allocated on the heap). Their lifetime is managed by the runtime.
Dynamic arrays are proper built-in types and far more general than Variants (and Variant arrays).
Delphi Basics also has more info on them.
Update
As Remy Lebeau commented, I should mention that a Variant array (and also an OleVariant array) is based on COM's SAFEARRAY structure, and thus can only be created with COM/OLE-compatible data types, even though Delphi's Variant can hold non-COM/OLE types.

Any efficient way to convert TArray<string> to TStringDynArray?

Quite a big portion of my code (delphi-dutil, etc.) uses TStringDynArray. Now I want to convert all keys a TDictionary<string, string> to a TStringDynArray. Unfortunately I only found TDictionary.Keys.ToArray, which is TArray<T>.
I know I can write a simple copy function to do a raw content copy, but my TStringDynArray is usually very big (roughly 10000 entries), so I am looking for an efficient way to convert TArray<string> to TStringDynArray.
function ConvertToStringDynArray(const A: TArray<string>): TStringDynArray;
var
I: Integer;
begin
assert(A <> nil);
SetLength(Result, Length(A));
for I := 0 to Length(A) - 1 do
Result[I] := A[I];
end;
The best solution is to convert all dynamic arrays in your codebase to be TArray<T>. This is best because generic types have much less stringent type compatibility rules. So I suggest that you consider taking this step across your codebase.
However, that's only viable in the code that you control. When interfacing with code and libraries that you cannot modify you have a problem.
At the root of all this, all these types are dynamic arrays and share a single, common, implementation. This means that the blockage is at the language level rather than implementation. So a cast can be used, safely, to make the compiler accept you assignments.
var
DynArr: TStringDynArray;
GenericArr: TArray<string>;
....
DynArr := TStringDynArray(GenericArr);
GenericArr := TArray<string>(DynArr);
You can use these casts in function call arguments.
Because you are suppressing type checking, you really don't want to scatter these casts all over your code. Centralise them into helper functions. For instance:
function StringDynArray(const arr: TArray<string>): TStringDynArray;
begin
Result := TStringDynArray(arr);
end;
One final comment: this related question may be of interest: Is it safe to type-cast TArray<X> to array of X?

Pass multidimensional array as parameter to Postgresql function

I'm trying to maintain a Php application with a PostgreSQL database. At one point, a stored procedure is called, lets say function_x and inside function_x, function_y is called; function_y is passed a variable named parameter_1, and the definition of parameter_1 is:
parameter_1 numeric[][3] := {};
I'm trying to do a select function_y directly on the command line (or pgadmin) but I'm having problems passing an empty array into the function. according to the docs you have to use variadic but so I tried:
select function_y(581, 'CPN-00000000001-0000', 'TPN-00000000001-0001', 100, 2013, variadic arr := array[]);
But I got this error:
ERROR: cannot determine type of empty array
I tried different approaches but nothing works. How can I pass a multidimensional array as a parameter at a query?
1) You can, but you do not have to use VARIADIC parameters for array variables. You'd have to use it in the declaration of the function, not in the call, though.
2) Postgres array variables ignore dimensions in the definition. I quote the manual here:
The current implementation does not enforce the declared number of
dimensions either. Arrays of a particular element type are all
considered to be of the same type, regardless of size or number of
dimensions. So, declaring the array size or number of dimensions in
CREATE TABLE is simply documentation; it does not affect run-time behavior.
3) This is invalid syntax:
parameter_1 numeric[][3] := {};
Single quotes are required:
parameter_1 numeric[][3] := '{}';
Which is effectively the same as
parameter_1 numeric[] := '{}';
More details, code examples and links in this closely related answer:
Return rows matching elements of input array in plpgsql function

Resources