Help language development. Donate to The Perl Foundation

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

README.pod6
=begin pod

![Build Status](https://github.com/Kaiepi/ra-annotations/actions/workflows/test.yml/badge.svg)

=head1 NAME

annotations - Thread-safe static buffer

=head1 SYNOPSIS

=begin code :lang<raku>
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 --> Str) { ... }
    }

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

        my %DICTIONARY := $?CLASS.dictionary;

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

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

        my %DICTIONARY := $?CLASS.dictionary;

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

# 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 = Map.new: LATIN Z=> @ALPHABET;

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

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

        method translate(::?CLASS: Str:D $letter --> Str) {
            %DICTIONARY{$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.
=end code

=head1 DESCRIPTION

C<annotations> is a collection of containers in a package trench coat. Through
C<MetamodelX::AnnotationHOW>, a C<Positional> or C<Associative> container may
be associated with any kind of type, regardless of whether or not it actually
can support stashing. These can be retrieved with C<ANN>, and appended to via
the infix C<=>, C<annotate>, and C<graffiti> operators (see C<t/02-direct.t>
for an example of C<graffiti>).

Importing C<annotations> can either create an C<annotation> declarator with
C«<declare>» or override another (e.g. C«<role>») with C«<supersede>» (though
this produces an erroneous deprecation warning as of v2020.07). As
demonstrated, the C«<direct>» and C«<symbolic>» arguments determine the mode of
assignment to a package's C<ANN>. Finally, a package declarator must be
provided in order to retrieve its HOW.

=head1 AUTHOR

Ben Davies (Kaiepi)

=head1 COPYRIGHT AND LICENSE

Copyright 2022 Ben Davies

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.

=end pod