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);
}

      

+3


source to share


1 answer


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)

+1


source







All Articles