Help language development. Donate to The Perl Foundation

annotations zef:Kaiepi last updated on 2022-08-26

use v6.e.PREVIEW;

my constant LATIN = 'a'..'z';

module Upper {
    use annotations <declare symbolic class>;
    # We may now declare a class with a symbolic object buffer associated with
    # it. This can be retrieved by parameterizing ANN with our type object,
    # which is typically knowable at compile-time.

    role Alphabet[@LOOKUP is raw] is repr<Uninstantiable> {
        # The annotated operator makes some allocations in an annotation's ANN
        # buffer eagerly given some Str-coercive objects. These become IntStr:D
        # symbols, which is to say they're a name and a position in the buffer
        # for this mixin. Because we take care of this immediately, this can
        # act as our compile-time check for whether or not we really are
        # composing an annotation. Such allocating in ANN is thread-safe.
        my @SYMBOLS := $?CLASS annotate @LOOKUP;

        # The bare slots can be fetched by ANN's list method given these
        # symbols. Because these carry containers regardless of whether or not
        # a value is being stored, this can always be assigned to dynamically.
        method alphabet(::?CLASS: --> List:D) {
            ANN[$?CLASS].list: :of(@SYMBOLS)

        # Likewise, these symbols can form the keys of a map by the ANN's hash
        # method.
        method dictionary(::?CLASS: --> Map:D) {
            ANN[$?CLASS].hash: :of(@SYMBOLS)

        # This will find a letter given the key of a booked symbol.
        method translate(::?CLASS: Str:D $letter --> Str) {

    annotation Half does Alphabet[LATIN] is repr<Uninstantiable> {
        CHECK $?CLASS.alphabet = 'A'..'Z';

    annotation Full does Alphabet[LATIN] is repr<Uninstantiable> {
        CHECK $?CLASS.alphabet = 'A'..'Z';

# Despite the inner alphabet list being static, public, mutable state by
# technicality, its inner Binder slots each only respects its first assignment.
put Upper::Full.alphabet = Upper::Half.alphabet; # OUTPUT:
# A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
put Upper::Full.translate: 'a'; # OUTPUT:
# A

# But because we can achieve all this with static input alone, we can write a
# cheaper annotation.
module LowerPsychUpper {
    use annotations <declare direct class>;
    # Now the ANN buffer is purely a buffer. Direct annotations' ANN buffer is
    # a lower level construct compared to before; because we get references
    # over symbols, we generally need to track any value bound ourselves.

    annotation Full is repr<Uninstantiable> {
        # Note the RW array this time around.
        my constant @ALPHABET = $?CLASS annotate ['a'..'z'];

        my constant %DICTIONARY = LATIN Z=> @ALPHABET;

        method alphabet(::?CLASS: --> List:D) { @ALPHABET }

        method dictionary(::?CLASS: --> Map:D) { %DICTIONARY }

        method translate(::?CLASS: Str:D $letter --> Str) {
            %DICTIONARY.AT-KEY: $letter

# Unlike before, the RW containers provided by the RW array we annotated are
# preserved, so an assignment will carry through.
put LowerPsychUpper::Full.alphabet = Upper::Full.alphabet; # OUTPUT:
# A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
put LowerPsychUpper::Full.translate: 'z'; # OUTPUT:
# Z

# In this example, we have code resembling what's possible to write with OUR.
# Unlike a Stash in a WHO or a PseudoStash, ANN errs more toward order and
# immutability, but deconts of a Scalar are cheaper than those of a wrapper
# Proxy. An OUR-scoped value skips a call we need otherwise on top of this, and
# would thus be more efficient if the WHO Stash's inherent mutability is OK.