Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using captured types to type a class's attributes

I've followed the directions from the answers to the SO question How can classes be made parametric in Perl 6?. However, I've hit some soft roadblock; I'm trying to type an internal class's attribute using the type capture and getting the following error:

Died with X::TypeCheck::Assignment
  in submethod BUILDALL at ...
  in method insert at ...
  in block <unit> at ...

In the following example, I've typed the class BinaryNode's $.item attribute (with T) but doing so causes the above error:

class BinarySearchTree {
    my role BTSImpl[::T] {
        my class BinaryNode is rw {
            has T $.item; 
            has BinaryNode $.left;
            has BinaryNode $.right;
        }

        method create-node( T $x ) {
            BinaryNode.new(item => $x)
        }
    }

    method ^parameterize(Mu:U \this, Mu \T) {
        my $type := this.^mixin: BTSImpl[T];
        $type.^set_name: this.^name ~ '[' ~ T.^name ~ ']';
        $type
    }
}

my $bst = BinarySearchTree[Int].new;
$bst.create-node(6);
like image 317
Luis F. Uceta Avatar asked Jan 25 '23 07:01

Luis F. Uceta


1 Answers

First off, there's almost no need to perform the class + ^parameterize + role trick. It appears in some of the internals because it helps deal with some bootstrapping problems (the kind of fun one has when defining a language in terms of itself). However, in normal Raku code, just write a parametric role instead of a class. From the point of view of the consumer, there's usually no difference; one can:

  • Call .new on it to make an instance (which actually creates a class, known as a "pun", behind the scenes and makes the instance of that)
  • In fact, call any method on the type object with the same result; new isn't special
  • Inherit from it (again, it works on the automatically produced class)

With the added bonus that somebody can also compose it instead of inheriting.

Secondly, there's no relationship between a class defined inside of a role and the enclosing role (this is a general principle: nesting of one package inside of another doesn't imply any relationship between them at an object model level). Thus we need to make that separately generic and instantiate it.

These two get us to:

role BinarySearchTree[::T] {
    my role BinaryNode[::T] is rw {
        has T $.item;
        has BinaryNode $.left;
        has BinaryNode $.right;
    }

    method create-node( T $x ) {
        BinaryNode[T].new(item => $x)
    }
}

my $bst = BinarySearchTree[Int].new;
$bst.create-node(6);

Which really should work, but the compiler seems to get the timing wrong on the BinaryNode[T]. We can work around that by just forcing it to delay the paramerterization until runtime; there's many ways we may do that, but writing BinaryNode[$(T)] compact and cheap (optimizes into pretty much no extra cost). Thus giving a working solution:

role BinarySearchTree[::T] {
    my role BinaryNode[::T] is rw {
        has T $.item;
        has BinaryNode $.left;
        has BinaryNode $.right;
    }

    method create-node( T $x ) {
        BinaryNode[$(T)].new(item => $x)
    }
}

my $bst = BinarySearchTree[Int].new;
$bst.create-node(6);
like image 121
Jonathan Worthington Avatar answered Feb 27 '23 01:02

Jonathan Worthington