Both of them coerce the arguments to a Bool value. The difference is in their operator precedence. You cannot say for sure what is the precedence if you only look at the Bool.pm file. You will find more details in the src/Perl6/Grammar.nqp file describing the Perl 6 language grammar. Here are the fragments we need:

These look complex but let’s first concentrate only on the last part of the token definitions: <O(|%loose_unary)> and <O(|%symbolic_unary)>. Obviously, these are what define the rules for precedence. You can find a list of about 30 different kind of precedences in the same file:

Let’s avoid digging deeper into how it works at the moment. Looking at the list you can guess that the letters k, j, h, and g define the preference order of different kinds of preference rules. As well a right or left dictate the associativity of the operators.

So, the so operator has the loose unary precedence level and the ? operator has a higher symbolic unary precedence.

The old conditional operator

Before we wrap up for today, let’s look at another interesting place where the single question mark can be caught in the Perl 6 program. I am talking about the following token in the grammar (notice that this time this is for an infix, not for a prefix):

Re-compile Rakudo and make a few tests with both ? and so (you’ll get some numbers printed before the prompt appears):

$ ./perl6
> my Bool $b;
(Bool)
> ?$b;
2
> so $b;
5
>

At the moment, there are no surprises. For an undefined Boolean variable, those subs are called that have the (Bool:U) signature.

Now, try an integer:

> my Int $i;
(Int)
> ?$i;
3
> so $i;
6

Although the variable is of the Int type, the compiler calls the subs from Bool.pm (notice that those functions are regular subs, not the methods of the Bool class). This time, the subs having the (Mu) signature are called, as Int is a grand-grandchild of Mu (via Cool and Any). For the undefined variable, the subs call the Bool method from the Mu class.

Call with arguments

The vertical bar, which we have already seen earlier, is a signature that captures argument lists with no type checking. It is not possible to omit it and leave empty parentheses, as in that case the routine can only be called without arguments.

Inside, some NQP-magic happens but that is quite readable for us. If there are arguments, the routine loops over them, shifting the next argument in each cycle.

Then, there is an attempt to get the name, type and content:

my $name := try $var.VAR.?name;
my $type := $var.WHAT.^name;

Notice the presence of try and ? in the method call. We already saw the pattern when we were taking about string interpolation. The ?name is only called on an object if the method exists there, and does not generate an error if not.

The result depends on whether an object is a lazy list or not. For example, try dumping an infinite range:

$ ./perl6 -e'dd 1..∞'
(1, 2, 3, 4, 5, 6, 7, 8, 9, 10... lazy list)

Only the first ten items are listed. For a non-lazy object, the perl method is called.

Finally, the result is printed to STDERR:

note $name ?? "$type $name = $what" !! $what;

Call with no arguments

The second branch of the dd routine is triggered when there are no arguments. In that case, the routine tries to give some information about the place where it is called. Look at the following example:

sub f() { dd }
f;

The result of running this program shows the name and the signature of the function:

sub f()

A good use case can be thus to use dd in multi-functions instead of printing manual text messages.

multi sub f(Int) { dd }
multi sub f(Str) { dd }
f(42);
f('42');

Run the program, and it prints an extremely useful debugging information:

In the previous articles, we’ve seen that the undefined value cannot be easily interpolated in a string, as an exception occurs. Today, our goal is to see where exactly that happens in the source code of Rakudo.

So, as soon as we’ve looked at the Boolean values, let’s continue with them. Open perl6 in the REPL mode and create a variable:

$ perl6
To exit type 'exit' or '^D'
> my $b
(Any)

The variable is undefined, so be ready to get an exception when interpolating it:

> "$b"
Use of uninitialized value $b of type Any in string context.
Methods .^name, .perl, .gist, or .say can be used to stringify it to something meaningful.
in block at line 1

Interpolation uses the Str method. For undefined values, this method is absent in the Bool class. So we have to trace back to the Mu class, where we can see the following collection of base methods:

The proto-definition gives the pattern for the Str methods. The vertical bar in the signature indicates that the proto does not validate the type of the argument and can also capture more arguments.

In the Str(Mu:U) method you can easily see the text of the error message. This method is called for the undefined variable. In our case, with the Boolean variable, there’s no Str(Bool:U) method in the Bool class, so the call is dispatched to the method of the Mu class.

Today, we are continuing reading the source codes of the Bool class: src/core/Bool.pm, and will look at the methods that calculate the next or the previous values, or increment and decrement the values. For the Boolean type, it sounds simple, but you still have to determine the behaviour of the edge cases.

pred and succ

In Perl 6, there are two complementary methods: pred and succ that should return, correspondingly, the preceding and the succeeding values. This is how they are defined for the Bool type:

When you read the sources, you start slowly understand that many strangely behaving bits of the language may be well explained, because the developers have to think about huge combinations of arguments, variables, positions, etc., about which you may not even think when using the language.

The prefix forms simply set the value of the variable to either True or False, and it happens for both defined and undefined variables. The is rw trait allows modifying the argument.

We see a new element of syntax—the return value is mentioned after an arrow in the sub signature:

(Bool:U $a is rw --> False)

The bodies of the operators that work on defined variables, are wordier. If you look at the code precisely, you can see that it avoids assigning the new value to a variable if, for example, a variable containing True is incremented.

As an exercise, let us improve your local Perl 6 by adding the gist method for undefined values. By default, it does not exist, and we saw that yesterday. It means that an attempt to interpolate an undefined variable in a string will be rejected. Let’s make it better.

Interpolation uses the Str method. It is similar to both gist and perl, so you will have no difficulties in creating the new version.

Today, we will be digging into the internals of the Bool type using the source code of Rakudo, available on GitHub.

Perl 6 is written in the Perl 6 and NQP (Not Quite Perl 6) languages, which makes it relatively easy to read the sources. Of course, there are many things that are not easy to understand or which are not reflected in the publicly available documentation of the Perl 6 language. Neither you can find the deep details in the Perl 6 books so far. Anyway, this is still possible with some intermediate understanding of Perl 6.

OK, so back to the src/core/Bool.pm file. It begins with a few BEGIN phasers that add some methods and multi-methods to the Bool class. We’ll talk about the details of metamodels and class construction next time. Today, the more interesting for us is what the methods of the Bool class are doing.

gist and perl

The gist and perl methods return the string representation of the object: gist is implicitly called when a variable is stringified, perl is supposed to be called directly. It works for any object in Perl 6, but of course, the behaviour should be defined somewhere. And here they are:

As you can see, the True string is returned by the gist method, while the perl method returns Bool::True.

Both methods are multi-methods, and in the above example, the version with a defined argument was used. If you look at the signatures, you will see that the methods are different in the way an argument is specified: Bool:D: or Bool:U:. The letters D and U stay for defined and undefined, correspondingly. The first colon adds an attribute to the type, while the second one indicates that the argument is actually an invocant.

So, different versions of the methods are triggered depending on whether they are called on a defined or an undefined Boolean variable. To demonstrate the behaviour of the other two variants, simply remove the initialiser part from the code:

my Bool $b;
say $b; # (Bool)
$b.perl.say; # Bool

As the variable $b has a type, Perl 6 knows the type of the object, on which it should call methods. Then it is dispatched to the versions with the (Bool:U:) signature because the variable is not defined yet.

When an undefined variable appears in the string, for example, say "[$b]", the gist method is not called. Instead, you get an error message.

Use of uninitialized value $b of type Bool in string context.
Methods .^name, .perl, .gist, or .say can be used to stringify it to something meaningful.
in block at bool-2.pl line 3
[]

The error message says that Perl knows of what type the variable was, but refuses to call a stringifying method.

That’s all for today. Next time, we’ll look at other methods defined for the Bool data type.