Help language development. Donate to The Perl Foundation
=begin pod  =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