Contexts and contextualizers
What are contexts and how to switch into them
A context is needed, in many occasions, to interpret the value of a container. In Raku, we will use context to coerce the value of a container into some type or class, or in general decide what to do with it, as in the case of the sink context. In general, recognizing an object that is in a certain context will force an implicit coercion by calling a specific method on it.
The sink context is equivalent to what in other languages is called
void context, that is, a context in which we throw (down the sink, as it were) the result of an operation or the return value from a block. In general, this context will be invoked in warnings and errors when a statement does not know what to do with that value.
my = -> ;; # OUTPUT: «WARNINGS:␤Useless use of $sub in sink context (line 1)␤»
In general, blocks will warn if evaluated in sink context; however, gather/take blocks are explicitly evaluated in sink context, with values returned explicitly using
take; however, sinking will explicitly throw away those values, so use sink only if you want to run a
gather statement for the side effects.
In sink context, an object will call its
sink method if present:
sub foofoo; # OUTPUT: sink called
This context, and probably all other contexts except sink above, are conversion or interpretation contexts in the sense that they take an untyped or typed variable and duck-type it to whatever is needed to perform the operation. In some cases that will imply a conversion (from Str to Numeric, for instance); in other cases simply an interpretation (IntStr will be interpreted as Int or as Str).
Number context is called whenever we need to apply a numerical operation on a variable.
my = "1 ";my = "3 ";say + ; # OUTPUT: «4␤»
In the code above, strings will be interpreted in numeric context as long as there are only a few digits and no other characters. It can have any number of leading or trailing whitespace, however.
Numeric context can be forced by using arithmetic operators such as
-. In that context, the
Numeric method will be called if available and the value returned used as the numeric value of the object.
my = True;my = False;say + ; # OUTPUT: «1␤»say .Numeric; # OUTPUT: «1␤»say .Numeric; # OUTPUT: «0␤»my = <a b c>;say True + ; # OUTPUT: «4␤»
In the case of listy things, the numeric value will be in general equivalent to
.elems; in some cases, like Thread it will return an unique thread identifier.
In a string context, values can be manipulated as strings. This context is used, for instance, for coercing non-string values so that they can be printed to standard output.
put ; # OUTPUT: something meaningful
Or when smartmatching to a regular expression:
put 333444777 ~~ /(3+)/; # OUTPUT: «｢333｣␤ 0 => ｢333｣␤»
In general, the
Str routine will be called on a variable to contextualize it; since this method is inherited from Mu, it is always present, but it is not always guaranteed to work. In some core classes it will issue a warning.
~ is the (unary) string contextualizer. As an operator, it concatenates strings, but as a prefix operator it becomes the string context operator.
my = [ [1,2,3], [4,5,6]];say ~; # OUTPUT: «1 2 3 4 5 6␤»
This will happen also in a reduction context, when
[~] is applied to a list
say [~] [ 3, 5+6i, Set(<a b c>), [1,2,3] ]; # OUTPUT: «35+6ic a b1 2 3␤»
In that sense, empty lists or other containers will stringify to an empty string:
say [~]  ; # OUTPUT: «␤»
~ acts also as buffer concatenation operator, it will have to check that every element is not empty, since a single empty buffer in string context will behave as a string, thus yielding an error.
say [~] Buf.new(0x3,0x33), Buf.new(0x2,0x22);# OUTPUT: «Buf:0x<03 33 02 22>␤»
my = Buf.new(0x3, 0x33);my = ;my = Buf.new(0x2,0x22);say [~] , , ;# OUTPUT: «Cannot use a Buf as a string, but you called the Stringy method on it
~ is putting in string context the second element of this list,
~ is going to be using the second form that applies to strings, thus yielding the shown error. Simply making sure that everything you concatenate is a buffer will avoid this problem.
my = Buf.new(0x3, 0x33);my = Buf.new();my = Buf.new(0x2,0x22);say [~] , , ; # OUTPUT: «Buf:0x<03 33 02 22>␤»
In general, a context will coerce a variable to a particular type by calling the contextualizer; in the case of mixins, if the context class is mixed in, it will behave in that way.
my = 1i but 'Unity in complex plane';put ; # OUTPUT: «Unity in complex plane␤»
but creates a mixin, which endows the complex number with a
put contextualizes it into a string, that is, it calls
Str, the string contextualizer, with the result shown above.
This context will force a variable to be interpreted as
say "Hey" if 7; # OUTPUT: «Hey␤»say "Ahoy" if "";
This context appears in expressions such as
while, and is equivalent to calling
so on these values.
say "Hey" if 7.so; # OUTPUT: «Hey␤»say "Ahoy" if not set().so; # OUTPUT: «Ahoy␤»
In general, non-zero, non-empty will be converted to
True; zero or empty will be equivalent to
.so can be defined to return any Boolean value we want, so this is just a rule of thumb.
! are the Boolean context operator and its negation respectively. They will force this context on an object.
say ? 0i; # OUTPUT: «False␤»say ! :true; # OUTPUT: «False␤»
There are actually several different list contexts, which are better explained in that page. In general, the list contextualizer is the comma
say (3,).^name; # OUTPUT: «List␤»
and the method called in that case is also
Any.list.^name; # OUTPUT: «List␤»say 3.list.^name; # OUTPUT: «List␤»say (^3).list; # OUTPUT: «(0 1 2)␤»
Item or scalar context will deal with complex pieces of data as if they were a single item. It is forced when you try to assign to a scalar variable
my = <1 2 3>;say "→ $_" for ; # OUTPUT: «→ 1 2 3␤»
It can be induced using the
$ operator, that acts as the contextualizer operator of by calling
item as a method or routine
.say for $(1,2,3); # OUTPUT: «(1 2 3)␤».say for (1,2,3).item; # OUTPUT: «(1 2 3)␤».say for item( 1..3 ); # OUTPUT: «1..3␤»
Itemization affects only their behavior in list context; however, they will still keep their
Positional role or other roles they might have:
$(1,2,3).elems.say; # OUTPUT: «3␤»say (1,2,3).item; # OUTPUT: «3␤»say $( key => 'value')<key>; # OUTPUT: «value␤»
Itemization containerizes values in a data structure keeping them, for instance, from being flattened into the surrounding list or data structure:
.say for (1, $(2,3), 4).flat; # OUTPUT: «1␤(2 3)␤4␤»say (1, b>, 2).elems; # OUTPUT: «3␤»
The itemizer operator will call the
.item method on the object; it can also be called as a subroutine. Since that is a method inherited from
Mu, objects of any class can be itemized.