Possible to declare generic function for both constrained and unconstrained arrays? - ada

Say I have a formal array type and a function to find the index of a given element in that array like so:
generic
type T is private;
type T_index is (<>);
type T_array is array(T_index range <>) of T;
function FindIndexGeneric(array: T_array; element: T; elementFound: out Boolean) return T_index;
Instantiating this for an unconstrained array such as string works fine:
function FindIndexString is new FindIndexGeneric
(T=>Character, T_index=>Positive, T_array=>String);
But the same doesn't work when I have a constrained array:
type userIDIndex is Integer range 1..6;
type userID is array(userIDIndex) of Character;
function FindIndexUserID is new FindIndexGeneric
(T=>Character, T_index=>userIDIndex, T_array=>userID);
-- error: expect unconstrained array in instantiation of "T_array"
I can make constrained arrays work by changing the formal array declaration line to:
type T_array is array(T_index) of T;
but then I cant instantiate unconstrained arrays with this.
Given the core logic in my array functions (FindIndexGeneric) are the same, I don't want to create multiple versions of it, one each for constrained and unconstrained arrays.
Is there a way to make a generic function operate on both constrained and unconstrained arrays?

I don't think there is a way to instantiate a generic with both constrained and unconstrained arrays, but there is a kind of work-around: you can instantiate with an unconstrained array, and define a constrained subtype of the unconstrained array type, and then you can call the instantiated generic function with a parameter of the constrained subtype. Like this:
generic
type T is private;
type T_index is (<>);
type T_array is array (T_index range <>) of T;
...
type userIDIndex is Integer range 1..6;
type userIDUnconstrained is
array (userIDIndex range <>) of Character;
subtype userID is userIDUnconstrained (userIDIndex);
function FindIndexUserID is new FindIndexGeneric
(T => Character,
T_index => userIDIndex,
T_array => userIDUnconstrained);
Then you can call FindIndexUserID with a parameter of the constrained (sub)type userID. However, note that in the body of the generic you should traverse the array parameter by iterating over its index range (arry'Range), not over the whole T_index.

Related

Idiomatic Way to Pass an Empty Enumeration to a Generic in Ada

I'm instantiating a generic package with an enumeration to access one of multiple values and use in subprogram overloading. I want to have a well-defined, compile-time checked set of values I can use and look up.
generic
-- Different types because we don't want to ensure we never put
-- beer in a wine class, or wine in a beer stein. Our inventory
-- never changes, for... reasons.
type Wine is (<>);
type Beer is (<>);
package Bar is
type Wine_Glass is null record;
type Beer_Stein is null record;
-- Unopened cases/bottles of each.
type Wine_Inventory is array (Wine) of Natural;
type Beer_Inventory is array (Beer) of Natural;
procedure Pour (G : in out Wine_Glass; W : in Wine);
procedure Pour (S : in out Beer_Stein; B : in Beer);
end Bar;
What's the idiomatic to describe an empty enumeration?
with Bar;
procedure Wine_Tasting is
type Sampling_Wine is (Tempranillo, Zinfandel, Merlot);
pragma Unreferenced (Tempranillo, Zinfandel, Merlot);
type No_Beer is (None);
package Wine_Tasting_Bar is new Bar(Wine => Sampling_Wine, Beer => No_Beer);
Stein : Wine_Tasting_Bar.Beer_Stein;
begin
Wine_Tasting_Bar.Pour (Stein, None); -- legal!
end Wine_Tasting;
Is there a way to describe this such that Beer is an enumeration with no values, so that Pour can never been called with a Beer?
You have to declare an enumeration type with at least two values, and then declare a subtype with no values. You use the subtype to instantiate the generic:
type Wine_Kind is (Red, White, Green);
type Beer_Base is (Ale, Lager);
subtype No_Beer is Beer_Base range Lager .. Ale;
package Wine_Bar is new Bar (Wine => Wine_Kind, Beer => No_Beer);
According to Ada Reference Manual section 3.5.1 an enumeration type is described as
enumeration_type_definition ::= (enumeration_literal_specification
{, enumeration_literal_specification})
The first enumeration_literal_specification is required, further enumeration_literal_specifications are optional. From this syntax description I assert there is no way to declare a enumeration type with no enumeration_literal_specifications.

Julia: Parametric types with inner constructor: new and typeof

Trying to understand parametric types and the new function available for inner methods. The manual states "special function available to inner constructors which created a new object of the type". See the section of the manual on new here and the section of the manual on inner constructor methods here.
Consider an inner method designed to calculate the sum of x, where x could be, say, a vector or a tuple, and is given the parametric type T. A natural thing to want is for the type of the elements of x to be inherited by their sum s. I don't seem to need new for that, correct?
struct M{T}
x::T
s
function M(x)
s = sum(x)
x,s
end
end
julia> M([1,2,3])
([1, 2, 3], 6)
julia> M([1.,2.,3.])
([1.0, 2.0, 3.0], 6.0)
julia> typeof(M([1.,2.,3.]))
Tuple{Vector{Float64}, Float64}
Edit: Correction! I intended to have the last line of the inner constructor be M(x,s)... It's still an interesting question, so I won't correct it. How does M(x,s) differ from new{typeof(x)}(x,s)?
One usage of new I have seen is in combination with typeof(), something like:
struct M{T}
x::T
s
function M(x)
s = sum(x)
new{typeof(x)}(x,s)
end
end
julia> M([1,2,3])
M{Vector{Int64}}([1, 2, 3], 6)
julia> M([1.,2.,3.])
M{Vector{Float64}}([1.0, 2.0, 3.0], 6.0)
What if wanted to constrain s to the same type as x? That is, for instance, if x is a vector, then s should be a vector (in this case, a vector of one element). How would I do that? If I replace the last line of the inner constructor with x, new{typeof(x)}(s), I get the understandable error:
MethodError: Cannot `convert` an object of type Int64 to an object of type Vector{Int64}
Here are the rules:
If you are writing an outer constructor for a type M, the constructor should return an instance of M by eventually calling the inner constructor, like this: M(<args>).
If you are writing an inner constructor, this will override the default inner constructor. So you must return an instance of M by calling new(<args>).
The new "special function" exists to allow the construction of a type that doesn't have a constructor yet. Observe the following example:
julia> struct A
x::Int
function A(x)
A(x)
end
end
julia> A(4)
ERROR: StackOverflowError:
Stacktrace:
[1] A(::Int64) at ./REPL[3]:4 (repeats 79984 times)
This is a circular definition of the constructor for A, which results in a stack overflow. You cannot pull yourself up by your bootstraps, so Julia provides the new function as a way to circumvent this problem.
You should provide the new function with a number of arguments equal to the number of fields in your struct. Note that the new function will attempt to convert the types of its inputs to match the declared types of the fields of your struct:
julia> struct B
x::Float64
B(x) = new(x)
end
julia> B(5)
B(5.0)
julia> B('a')
B(97.0)
julia> B("a")
ERROR: MethodError: Cannot `convert` an object of type String to an object
of type Float64
(The inner constructor for B above is exactly the same as the default inner constructor.)
When you're defining parametric types, the new function must be provided with a number of parameters equal to the number of parameters for your type (and in the same order), analogously to the default inner constructor for parametric types. First observe how the default inner constructor for parametric types is used:
julia> struct Foo{T}
x::T
end
julia> Foo{String}("a")
Foo{String}("a")
Now if you were writing an inner constructor for Foo, instead of writing Foo{T}(x) inside the constructor, you would replace the Foo with new, like this: new{T}(x).
You might need typeof to help define the constructor, but often you don't. Here's one way you could define your M type:
struct M{I, T}
x::I
s::T
function M(x::I) where I
s = sum(x)
new{I, typeof(s)}(x, s)
end
end
I'm using typeof here so that I could be any iterable type that returns numbers:
julia> typeof(M(1:3))
M{UnitRange{Int64},Int64}
julia> g = (rand() for _ in 1:10)
Base.Generator{UnitRange{Int64},var"#5#6"}(var"#5#6"(), 1:10)
julia> typeof(M(g))
M{Base.Generator{UnitRange{Int64},var"#5#6"},Float64}
Note that providing the parameters for your type is required when you are using new inside an inner constructor for a parametric type:
julia> struct C{T}
x::Int
C(x) = new(x)
end
ERROR: syntax: too few type parameters specified in "new{...}" around REPL[6]:1
Remember, a constructor is designed to construct something. Specifically, the constructor M is designed to construct a value of type M. Your example constructor
struct M{T}
x::T
s
function M(x)
s = sum(x)
x,s
end
end
means that the result of evaluating the expression M([1 2 3]) is a tuple, not an instance of M. If I encountered such a constructor in the wild, I'd assume it was a bug and report it. new is the internal magic that allows you to actually construct a value of type M.
It's a matter of abstraction. If you just want a tuple in the first place, then forget about the structure called M and just define a function m at module scope that returns a tuple. But if you intend to treat this as a special data type, potentially for use with dynamic dispatch but even just for self-documentation purposes, then your constructor should return a value of type M.

How can I prevent a type being used as a map key?

I have a type that can be used as a map key, but I want to prevent this from occurring. I assumed that if the type contained a private member it wouldn't be possible from other packages, but this appears to work anyway. What's the best way to make the type unusable as a map key?
type MyType struct {
A *A
b b
preventUseAsKey ?
}
I don't see any benefit of disallowing a type being used as a key. It is just an option which may or may not be used, the type will not be any better or smaller or faster just because you forbid to use it as a map key.
But if you want to do it: Spec: Map types:
The comparison operators == and != must be fully defined for operands of the key type; thus the key type must not be a function, map, or slice.
So if you can violate the terms of the comparison operators, you implicitly get what you want. You have a struct, terms for the struct types:
Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.
So struct values are only comparable (and thus can only be used as keys in maps) if all their fields are comparable. Simply add a field whose type is not comparable.
Slice, map, and function values are not comparable.
So for example add a field whose type is a slice, and you're done:
type MyType struct {
S string
i int
notComparable []int
}
Attempting to use the above MyType as a key:
m := map[MyType]int{}
You get a compile-time error:
invalid map key type MyType
Note:
I wrote about not having any benefit of forbidding the type being a key. It's more than that: from now on you won't be able to use comparison operators on values of your type anymore (because of the extra, non-comparable field), so e.g. you lose the option to compare those values:
p1, p2 := MyType{}, MyType{}
fmt.Println(p1 == p2)
Compile-time error:
invalid operation: p1 == p2 (struct containing []int cannot be compared)
Note that with a little trick you could still preserve the comparable nature of your type, e.g. by not exporting your type but a wrapper type which embeds the original one; and add the extra, non-comparable type to the wrapper type, e.g.:
type myType struct {
S string
i int
}
type MyType struct {
myType
notComparable []int
}
func main() {
p1, p2 := MyType{}, MyType{}
fmt.Println(p1.myType == p2.myType)
}
This way your myType can remain comparable but still prevent the exported, wrapper MyType type to be used as key type.
Your type should not be comparable in order to be unfit as a map key.
Slice, map, and function values are not comparable
See Key Type:
Notably absent from the list are slices, maps, and functions; these types cannot be compared using ==, and may not be used as map keys.
So if your type is a slice, map or function, you should get what you need.
It could be an "alias" (defining a new named type):
type StringSliceWrap []string
type MyFunc func(i int)
That alias would not be used as a map key.
Update 2017: Brad Fitzpatrick give this tip (adding a slice in your struct) to make sure your type struct is not comparable: See play.golang.org:
package main
// disallowEqual is an uncomparable type.
// If you place it first in your struct, you prevent == from
// working on your struct without growing its size. (Don't put it
// at the end; that grows the size of the struct)
type disallowEqual [0]func()
type T struct {
_ disallowEqual
Foo string
Bar int
}
func main() {
var t1 T
var t2 T
println(t1 == t2)
}
T cannot be used as amp key now!

Vector with tagged types

Can I have a vector filled with both, type A and B? I will fill it only with one type, so I am always sure, what I will get out of it. But it would make many things very easy for me, if I can define the vector once.
type T is tagged null record;
type A is new T with
record
x : Integer;
end record;
type B is new T with
record
y : Integer;
end record;
package Some_Vector is new Ada.Containers.Indefinite_Vectors (
Index_Type => Positive,
Element_Type => T <- ???
);
You can say:
package Some_Vector is new Ada.Containers.Indefinite_Vectors (
Index_Type => Positive,
Element_Type => T'Class
);
Some_Vector is now able to hold elements of type T or any type derived from it, including A or B. There's no requirement that all the elements have to be of the same type, as long as they're all derived from T; so if you don't really care whether this property is enforced, the above should work. If you really want the compiler to enforce that all elements are the same type, then you should simply declare two packages, A_Vector and B_Vector, for vectors of the two types; but then there's no way to write a "class-wide" type name that could refer to either an A_Vector or a B_Vector.
If you really want to combine both--have a vector type that could refer either to a vector of A or a vector of B, but still enforce that all elements of the vector have the same type, then I think this could be done if you define your own vector type and perform the needed check at run time, but it could get complicated. This compiles, but I haven't tested it:
generic
type Elem is new T with private;
package Sub_Vectors is
type Sub_Vector is new Some_Vector.Vector with null record;
overriding
procedure Insert (Container : in out Sub_Vector;
Before : in Some_Vector.Extended_Index;
New_Item : in T'Class;
Count : in Ada.Containers.Count_Type := 1)
with Pre => New_Item in Elem;
end Sub_Vectors;
package body Sub_Vectors is
procedure Insert (Container : in out Sub_Vector;
Before : in Some_Vector.Extended_Index;
New_Item : in T'Class;
Count : in Ada.Containers.Count_Type := 1) is
begin
Some_Vector.Insert
(Some_Vector.Vector(Container), Before, New_Item, Count);
end Insert;
end Sub_Vectors;
Unfortunately, you'd have to override every Insert and Replace_Element operation that could put an element into the vector. After you do all this, though, you can instantiate Sub_Vectors with Elem => A and with Elem => B, and the class Some_Vector.Vector'Class would be a class-wide type that would include both Sub_Vector types in the instance packages.
If you really want the compiler to enforce that all elements are the
same type, then you should simply declare two packages, A_Vector and
B_Vector, for vectors of the two types; but then there's no way to
write a "class-wide" type name that could refer to either an A_Vector
or a B_Vector.
You can, however, have a vector that points only to the A or B subtrees of the hierarchy:
type T is tagged null record;
type A is new T with null record;
type B is new T with null record;
type C is new T with null record;
type TAB is access all T'Class
with Dynamic_Predicate =>
TAB = null or else
(TAB.all in A'Class or TAB.all in B'Class);
Above yields the TAB Type which must be a [pointer to] an A'Class or B'Class, which you should be able to use in your vector. -- The only problem I've run into is you have to use GNAT's 'Unchecked_Access to get the access values of objects (due, I think, to my quick and dirty testing).

Array of variant records in Ada

I want to declare an array with element-type of a variant record.
Something like this:
type myStruct (theType : vehicleType) is
record
...
when car => numOfWheels : Positive;
when others => null;
end record;
myArray : array (Positive range <>) of myStruct;
But in this case I get error.
It only allows this:
myArray : array (1.100) of myStruct(Car); //such as
So how to solve the index-problem and how to describe an array of a variant record's type without giving it's discriminant?
The example above will not compile. Here is a correct version (I changed mystruct to Integer for simplicity):
procedure test_array is
subtype Vehicle_Array_Index is Integer range 1..100; --// Maximum size is 100
type Arr_T is array (Vehicle_Array_Index range <>) of Integer;
type Vehicle_Array (Size: Vehicle_Array_Index := 1) is record
Vehicles : Arr_T(1..Size);
end record;
begin
null;
end;
One of the errors was that you cannot have anonymous arrays inside records, and second, you should use the discriminant to constrain the array inside.
As mentioned above this is not a good solution for varying-length arrays as most likely you will get a maximum sized array anyway. If you want arrays with dynamically determined sizes you can use blocks for that.
declare
a: array(1..n) of integer; -- n can be a variable here
begin
--some code using a
end;
It also works in the declaration parts of procedures and functions where n can be a parameter passed to the subprogram (one of the advantages Ada has over C/C++). And of course you can just allocate arrays dynamically on the heap using allocators.
If you want to be able to create objects of a discriminated type and change (or figure out) what the type is at runtime, you have to give the discriminant a default value in the declaration.
The reason for this is that Ada doesn't want to have to worry about dealing with uninitialized discriminated objects that it can't even figure out the size and valid record fields of.
For this reason, and some reasons I go into a bit in the comments, Ada discriminated records aren't actually very useful in mixed language programming (eg: exactly duplicating a C union). They can be handy on their own terms though.
For the example you give, you'd do the following (warning: Not compiled):
type myStruct (theType : vehicleType := car) is
record
...
when car => numOfWheels : Positive;
when others => null;
end record;
Then you could set one of the array values at runtime thusly:
myArray(20) := (theType => car,
field1 => myArray(20).field1,
... , --// Fill in the rest of the fields by name
numberOfWheels => 4);
As for this part:
myArray : array (Positive range <>) of myStruct;
You cannot declare actual array objects with an indeterminate range like this. You could declare a type that way, but an object has to have an actual size. If you want an array of varying length, you can once again use a variant record. (Again, not compiled)
subtype Vehicle_Array_Index is Integer range 1..100; --// Maximum size is 100
type Vehicle_Array (Vehicle_Array_Index : Size := 1) is record
Vehicles : array (Vehicle_Array_Index range <>) of myStruct;
end record;
Oh, and one more thing. You aren't doing this in your example, but if you ever want to use your discriminant to size an array like above, beware. When you declare objects of that type (again, assuming you used a default for the discriminant), the compiler will try to reserve enough space for the largest possible size you could ever feed it a value for. That makes it a Very Bad Idea to make a discriminated array indexed by something with a huge range like Integer or Positive. I know computers are bigger these days, but still most folks don't have 4 gig to spare for one silly little array. So if you are using your discriminant as an array index, it would be a good idea to create a smaller subtype of Integer to use for the type of the discriminant.

Resources