Typed array from non-printable array
Let's say we have the following multi sub
:
multi sub abc(Int @array) { say 10, ' ', @array; }
multi sub abc(Array[Int] @array) { say 20, ' ', @array; }
multi sub abc(Str @array) { say 30, ' ', @array; }
multi sub abc(Array[Str] @array) { say 40, ' ', @array; }
As mentioned in this question , calling them with typed arrays can be verbose:
abc Array[Int].new([1,2,3]);
abc Array[Array[Int]].new([Array[Int].new([1,2,3]), Array[Int].new([2,3,4])]);
It would be nice if the type could be inferred from the literal so that we can do something like this:
abc typed([1,2,3]);
abc typed([[1,2,3],[2,3,4]]);
abc typed(['a', 'b', 'c']);
abc typed([['a', 'b', 'c'], ['b', 'c', 'd']]);
Moving on, add a clause that does type inference for us:
multi sub abc(@array) { abc typed(@array); }
Now we can get the full output without any additional syntax:
abc [1,2,3];
abc [[1,2,3],[2,3,4]];
abc ['a', 'b', 'c'];
abc [['a', 'b', 'c'], ['b', 'c', 'd']];
The above displays the following:
10 [1 2 3]
20 [[1 2 3] [2 3 4]]
30 [a b c]
40 [[a b c] [b c d]]
Below is a simple version typed
that works for:
-
Array[Int]
-
Array[Array[Int]]
-
Array[Str]
-
Array[Array[Str]]
My question is, how could you implement this type of inference? Is there a better approach? Is there a similar tool available?
sub type-of(\obj)
{
if obj.^name eq 'Array'
{
if obj.map({ type-of($_).^name }).all eq obj.map({ type-of($_).^name })[0]
{
my $type = type-of(obj[0]);
return Array[$type];
}
return Array;
}
if obj.^name eq 'Int' { return Int; }
if obj.^name eq 'Str' { return Str; }
}
sub typed(\obj)
{
if obj.^name eq 'Array'
{
return type-of(obj)(obj.List.map({ $_.&typed }).Array);
}
return (type-of(obj))(obj);
}
source to share
calling them with typed arrays can get verbose
Perhaps you overdid it a bit with type restrictions on everything?
You can also store the type in a constant, with a bit shorter code:
my constant AI = Array[Int];
dd AI.new([1,2,3]);
dd Array[AI].new([AI.new([1,2,3]), AI.new([2,3,4])]);
My question is, how would you start implementing this type of inference?
.WHAT
the pseudo-method returns an object of type invocant. So you can use that to get the type of the thing, and you can use =:=
the container's identity operator to figure out if you are dealing with Array
. (Alternatively, you can use ~~
smartmatch instead , and catch subclasses this way Array
as well).
Here's an example of implementing such use using a custom envelope operator:
sub circumfix:<β₯[ ]> (|c) {
my \array = circumfix:<[ ]>(|c);
return array unless try array.elems;
(my $type := array.head.WHAT) =:= Array
and $type := (array[0] = circumfix:<β₯[ ]>(array.head<>)).WHAT;
my $type-it := True;
for array.tail: *-1 {
(my $el-type := .WHAT) =:= Array
and $el-type := ($_ = circumfix:<β₯[ ]>(.<>)).WHAT;
next if $el-type =:= $type;
$type-it := False;
}
$type-it ?? Array[$type].new: array !! array
}
dd β₯[<1 2 3>];
dd β₯[<a b c>];
dd β₯[[1e0,2], [2,3], [3,3]];
dd β₯[[1,2], [2,3], [3,3]];
dd β₯[[[[1],],],];
# OUTPUT:
# Array[IntStr].new(IntStr.new(1, "1"), IntStr.new(2, "2"), IntStr.new(3, "3"))
# Array[Str].new("a", "b", "c")
# [[1e0, 2], Array[Int].new(2, 3), Array[Int].new(3, 3)]
# Array[Array[Int]].new(Array[Int].new(1, 2), Array[Int].new(2, 3), Array[Int].new(3, 3))
# Array[Array[Array[Array[Int]]]].new(Array[Array[Array[Int]]].new(Array[Array[Int]].new(Array[Int].new(1))))
We use |c
to capture the arguments and just give them to a bypass kernel [ ]
which will make it regular Array
for us.
We then try
, to find out if we Array
have any elements: if we don't, we won "Don't know how to parameterize it, and lazy things won't let us know .elems
, therefore try
.
At this point, we know that we have at least one element in ours Array
, so we will grab it and use it .WHAT
to get its type. We then use =:=
to check if it is Array
, and if it is, we just recurse on it, save the result, update $type
to the return type. Bit <>
is equal to deconstructArray
:
(my $type := array.head.WHAT) =:= Array
and $type := (array[0] = circumfix:<β₯[ ]>(array.head<>)).WHAT;
Next, we have a cycle for
. We start with the second element, since we already took care of the first when we figured out which type to use.
If any other elements are not of the same type as $type
, we cannot parameterize ours Array
, so we just loop through and check their types, doing the same business recursively on Arrays
. If we encounter any other type, we set the ( $type-it
) flag to indicate that the current Array
one should not be parameterized (we are still continuing the loop, so we recurse and parameterize any remaining Array
s).
Finally, if the flag is set, we use $type
to parameterize the new Array
c; otherwise, we return ours Array
as is.
The Simpsons:)
PS: It's worth noting that Perl 6 supports self-referential Arrays
and the above code freezes with, say, this setting:
my @a; @a[0] = @a;
dd β₯[@a];
You will need to implement something that marks what has already been seen; something similar to this (if you are converting NQP code to pure Perl 6)
source to share