This page was generated by Text::SmartLinks v0.01 at 2010-06-24 20:02:12 GMT.
(syn r31436)
[ Index of Synopses ]
Synopsis 3: Perl 6 Operators
Luke Palmer <luke@luqui.org>
Larry Wall <larry@wall.org>
Darren Duncan <darren@darrenduncan.net>
Created: 8 Mar 2004
Last Modified: 18 Jun 2010
Version: 210
For a summary of the changes from Perl 5, see "Changes to Perl 5 operators".
From t/spec/S03-operators/arith.t lines 46–313: (skip)
-
| # L<S03/Operator precedence>
|
| tryeq 13 % 4, 1;
|
| tryeq -13 % 4, 3;
|
| tryeq 13 % -4, -3;
|
| tryeq -13 % -4, -1;
|
|
|
| {
|
| tryeq 5 % 2.5, 0;
|
| tryeq 2.5 % 1, .5;
|
| }
|
|
|
|
|
| my $limit = 1e6;
|
|
|
| ok abs( 13e21 % 4e21 - 1e21) < $limit;
|
| ok abs(-13e21 % 4e21 - 3e21) < $limit;
|
| ok abs( 13e21 % -4e21 - -3e21) < $limit;
|
| ok abs(-13e21 % -4e21 - -1e21) < $limit;
|
|
|
| # Hmm. Don t forget the simple stuff
|
| tryeq 1 + 1, 2;
|
| tryeq 4 + -2, 2;
|
| tryeq -10 + 100, 90;
|
| tryeq -7 + -9, -16;
|
| tryeq -63 + +2, -61;
|
| tryeq 4 + -1, 3;
|
| tryeq -1 + 1, 0;
|
| tryeq +29 + -29, 0;
|
| tryeq -1 + 4, 3;
|
| tryeq +4 + -17, -13;
|
|
|
| # subtraction
|
| tryeq 3 - 1, 2;
|
| tryeq 3 - 15, -12;
|
| tryeq 3 - -7, 10;
|
| tryeq -156 - 5, -161;
|
| tryeq -156 - -5, -151;
|
| tryeq -5 - -12, 7;
|
| tryeq -3 - -3, 0;
|
| tryeq 15 - 15, 0;
|
|
|
| tryeq 2147483647 - 0, 2147483647;
|
| tryeq 0 - -2147483647, 2147483647;
|
|
|
| # No warnings should appear;
|
| {
|
| my $a;
|
| $a += 1;
|
| tryeq $a, 1;
|
| undefine $a;
|
| $a += -1;
|
| tryeq $a, -1;
|
| undefine $a;
|
| $a += 4294967290;
|
| tryeq $a, 4294967290;
|
| undefine $a;
|
| $a += -4294967290;
|
| tryeq $a, -4294967290;
|
| undefine $a;
|
| $a += 4294967297;
|
| tryeq $a, 4294967297;
|
| undefine $a;
|
| $a += -4294967297;
|
| tryeq $a, -4294967297;
|
| }
|
|
|
| {
|
| my $s;
|
| $s -= 1;
|
| tryeq $s, -1;
|
| undefine $s;
|
| $s -= -1;
|
| tryeq $s, +1;
|
| undefine $s;
|
| $s -= -4294967290;
|
| tryeq $s, +4294967290;
|
| undefine $s;
|
| $s -= 4294967290;
|
| tryeq $s, -4294967290;
|
| undefine $s;
|
| $s -= 4294967297;
|
| tryeq $s, -4294967297;
|
| undefine $s;
|
| $s -= -4294967297;
|
| tryeq $s, +4294967297;
|
| }
|
|
|
| # Multiplication
|
|
|
| tryeq 1 * 3, 3;
|
| tryeq -2 * 3, -6;
|
| tryeq 3 * -3, -9;
|
| tryeq -4 * -3, 12;
|
|
|
| {
|
| # 2147483647 is prime. bah.
|
|
|
| tryeq 46339 * 46341, 0x7ffea80f;
|
| tryeq 46339 * -46341, -0x7ffea80f;
|
| tryeq -46339 * 46341, -0x7ffea80f;
|
| tryeq -46339 * -46341, 0x7ffea80f;
|
| }
|
|
|
| # leading space should be ignored
|
|
|
| tryeq 1 + " 1", 2;
|
| tryeq 3 + " -1", 2;
|
| tryeq 1.2, " 1.2";
|
| tryeq -1.2, " -1.2";
|
|
|
| # divide
|
|
|
| tryeq 28 div 14, 2;
|
| tryeq 28 div -7, -4;
|
| tryeq -28 div 4, -7;
|
| tryeq -28 div -2, 14;
|
|
|
| is(9 div 4, 2, "9 div 4 == 2");
|
| is(-9 div 4, -3, "-9 div 4 == -3");
|
| is(9 div -4, -3, "9 div -4 == -3");
|
| is(-9 div -4, 2, "-9 div -4 == 2");
|
|
|
| # modulo
|
|
|
| tryeq 13 mod 4, 1;
|
| tryeq -13 mod 4, 3;
|
| tryeq 13 mod -4, -3;
|
| tryeq -13 mod -4, -1;
|
|
|
| # The example for sloppy divide, rigged to avoid the peephole optimiser.
|
| is_approx "20." / "5.", 4;
|
|
|
| tryeq 2.5 / 2, 1.25;
|
| tryeq 3.5 / -2, -1.75;
|
| tryeq -4.5 / 2, -2.25;
|
| tryeq -5.5 / -2, 2.75;
|
|
|
| # exponentiation
|
|
|
| is 2**2, 4;
|
| is 2.2**2, 4.84;
|
| is_approx 2**2.2, 4.59479341;
|
| is_approx 2.2**2.2, 5.66669577;
|
| is 1**0, 1;
|
| is 1**1, 1;
|
| isnt 2**3**4, 4096, "** is right associative";
|
|
|
| # test associativity
|
| is 2 ** 2 ** 3, 256, 'infix:<**> is right associative';
|
|
|
| {
|
| is_approx(-1, (0 + 1i)**2, "i^2 == -1");
|
|
|
| #?rakudo skip 'long rats'
|
| is_approx(-1, (0.7071067811865476 + -0.7071067811865475i)**4, "sqrt(-i)**4 ==-1" );
|
| is_approx(1i, (-1+0i)**0.5, '(-1+0i)**0.5 == i ');
|
| }
|
|
|
| {
|
| # Inf
|
| is Inf, Inf;
|
| is -Inf, -Inf;
|
| isnt Inf, -Inf;
|
| is (-Inf).abs, Inf;
|
| is Inf+100, Inf;
|
| is Inf-100, Inf;
|
| is Inf*100, Inf;
|
| is Inf / 100, Inf;
|
| is Inf*-100, -Inf;
|
| is Inf / -100, -Inf;
|
| is 100 / Inf, 0;
|
| is Inf**100, Inf;
|
| is Inf*0, NaN;
|
| is Inf - Inf, NaN;
|
| is Inf*Inf, Inf;
|
| is Inf / Inf, NaN;
|
| is Inf*Inf / Inf, NaN;
|
| is Inf**0, 1;
|
| is 0**0, 1;
|
| is 0**Inf, 0;
|
|
|
| my $inf1 = 100**Inf;
|
| is $inf1, Inf, "100**Inf";
|
| my $inf2 = Inf**Inf;
|
| is $inf2, Inf, "Inf**Inf";
|
|
|
| }
|
| # See L<"http://mathworld.wolfram.com/Indeterminate.html">
|
| # for why these three values are defined like they are.
|
| {
|
| is 0.9**Inf, 0, "0.9**Inf converges towards 0";
|
| is 1.1**Inf, Inf, "1.1**Inf diverges towards Inf";
|
| is 1**Inf, 1;
|
| }
|
|
|
| ##?pugs todo
|
| #flunk("1**Inf is platform-specific -- it's 1 on OSX and NaN elsewhere");
|
|
|
| {
|
| # NaN
|
| is NaN, NaN;
|
| is -NaN, NaN;
|
| is NaN+100, NaN;
|
| is NaN-100, NaN;
|
| is NaN*100, NaN;
|
| is NaN / 100, NaN;
|
| is NaN**100, NaN;
|
| is NaN+NaN, NaN;
|
| is NaN - NaN, NaN;
|
| is NaN*NaN, NaN;
|
| is NaN / NaN, NaN;
|
|
|
| is NaN+Inf, NaN;
|
| is NaN - Inf, NaN;
|
| is NaN*Inf, NaN;
|
| is NaN / Inf, NaN;
|
| is Inf / NaN, NaN;
|
|
|
| my $nan1 = NaN**NaN;
|
| is $nan1, NaN, "NaN**NaN";
|
| my $nan2 = NaN**Inf;
|
| is $nan2, NaN, "NaN**Inf";
|
| my $nan3 = Inf**NaN;
|
| is $nan3, NaN, "Inf**NaN";
|
| }
|
|
|
| =begin pod
|
|
|
| =head2 BEHAVIOUR OF DIVISION AND MODULUS WITH ZERO
|
|
|
| This test tests the behaviour of '%' and '/' when used with
|
| a zero modulus resp. divisor.
|
|
|
| All uses of a zero modulus or divisor should 'die', and the
|
| 'die' should be non-fatal.
|
|
|
| =end pod
|
|
|
| my $x;
|
|
|
| #?rakudo 2 todo 'modulo by zero'
|
| dies_ok( { say 3 % 0 }, 'Modulo zero dies and is catchable');
|
| dies_ok( { $x = 0; say 3 % $x; }, 'Modulo zero dies and is catchable with VInt/VRat variables');
|
| dies_ok( { $x := 0; say 3 % $x; }, 'Modulo zero dies and is catchable with VRef variables');
|
|
|
| dies_ok( { say 3 div 0 }, 'Division by zero dies and is catchable');
|
| dies_ok( { $x = 0; say 3 div $x; }, 'Division by zero dies and is catchable with VInt div VRat variables');
|
| dies_ok( { $x := 0; say 3 div $x; }, 'Division by zero dies and is catchable with VRef variables');
|
|
|
| # This is a rakudo regression wrt bignum:
|
| #?rakudo skip 'bigint'
|
| {
|
| my $f = 1; $f *= $_ for 2..25; say $f;
|
| ok $f == 15511210043330985984000000,
|
| 'Can calcualte 25! without loss of precision';
|
| ok 2**65 == 36893488147419103232,
|
| 'Can calcualte 2**65 without loss of precision';
|
| }
|
|
|
| # RT #73264
|
| # Rat literals are gone
|
| {
|
| ok 1/7 / 1/7 == 1/49, 'no more Rat literals, infix:</> has normal left assoc';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
From t/spec/S03-operators/precedence.t lines 5–198: (skip)
-
| # L<S03/Operator precedence>
|
|
|
| =begin pod
|
|
|
| Tests that each level bind tighter than the next by sampling some ops.
|
|
|
| In between each precedence level are some tests that demonstrate the
|
| proper separation of the two levels.
|
|
|
| =end pod
|
|
|
| plan 53;
|
|
|
|
|
| # terms
|
|
|
| # FIXME how do we test this?
|
|
|
| # postfix method
|
|
|
| my @a = 1,2,3;
|
| is(++@a[2], 4, "bare postfix binds tighter than ++");
|
| is(++@a.[2], 5, "dotted postfix binds tighter than ++");
|
|
|
| # autoincrement
|
|
|
| my $i = 2;
|
| is(++$i ** 2, 9, "++ bind tighter than **");
|
| is(--$i ** 2, 4, "-- does too");
|
|
|
| # exponentiation
|
|
|
| is(-2**2, -4, "** bind tighter than unary -");
|
| isa_ok(~2**4, Str, "~4**4 is a string");
|
|
|
| # symbolic unary
|
|
|
| is(!0 * 2, 2, "unary ! binds tighter than *");
|
| is(!(0 * 2), 1, "beh");
|
| is(?2*2, 2, "binary -> numify causes reinterpretation as, binds tighter than *");
|
|
|
| # multiplicative
|
|
|
| is(4 + 3 * 2, 10, "* binds tighter than binary +");
|
| is(2 - 2 div 2, 1, "div binds tighter than binary -");
|
| is(2 - 2 / 2, 1 / 1, "/ binds tighter than binary -");
|
|
|
| # additive
|
|
|
| is(1 ~ 2 * 3, 16, "~ binds looser than *");
|
| ok(?((1 ~ 2 & 12) == 12), "but tighter than &");
|
| ok(?((2 + 2 | 4 - 1) == 4), "and + binds tighter than |");
|
|
|
| # replication
|
|
|
| is(2 x 2 + 3, "22222", "x binds looser than binary +");
|
| is((2 x 2) + 3, 25, "doublecheck");
|
|
|
| # concatenation
|
|
|
| is(2 x 2 ~ 3, "223", "x binds tighter than binary ~");
|
| ok(?((2 ~ 2 | 4 ~ 1) == 41), "and ~ binds tighter than |");
|
|
|
| # junctive and
|
|
|
| ok( ?( (1 & 2 | 3) ==3), '& binds tighter than |');
|
| ok((!(1 & 2 | 3) < 2), "ditto");
|
| ok(?((1 & 2 ^ 3) < 3), "and also ^");
|
| ok(?(!(1 & 2 ^ 4) != 3), "blah blah blah");
|
|
|
| # junctive or
|
|
|
| #?rakudo todo 'non-associativeness of infix:<^> and |'
|
| { # test that | and ^ are on the same level but parsefail
|
| eval_dies_ok 'my Mu $a = (1 | 2 ^ 3)', '| and ^ may not associate';
|
| eval_dies_ok 'my Mu $a = (1 ^ 2 | 3)', '^ and | may not associate';
|
| };
|
|
|
| {
|
| my Mu $a = (abs -1 ^ -1); # read as abs(-1 ^ -1) -> (1^1)
|
| ok(!($a == 1), 'junctive or binds more tightly then abs (1)');
|
| }
|
|
|
| {
|
| my Mu $b = ((abs -1) ^ -1); # -> (1 ^ -1)
|
| ok($b == 1, "this is true because only one is == 1");
|
| };
|
|
|
| # named unary
|
|
|
| is((abs -1 .. 3), (1 .. 3), "abs binds tighter than ..");
|
| #is((rand 3 <=> 5), -1, "rand binds tighter than <=>"); # XXX rand N is obsolete
|
|
|
| # structural
|
|
|
| ok(0 < 2 <=> 1 < 2, "0 < 2 <=> 1 < 2 means 0 < 1 < 2");
|
|
|
| # chaining
|
|
|
| is((0 != 1 && "foo"), "foo", "!= binds tighter than &&");
|
| ok((0 || 1 == (2-1) == (0+1) || "foo") ne "foo", "== binds tighter than || also when chaning");
|
|
|
| # tight and (&&)
|
|
|
| # tight or (||, ^^, //)
|
|
|
| is((1 && 0 ?? 2 !! 3), 3, "&& binds tighter than ??");
|
| ### FIXME - need also ||, otherwise we don't prove || and ?? are diff
|
|
|
| # conditional
|
|
|
| {
|
| my $a = 0 ?? "yes" !! "no";
|
| is($a, "no", "??!! binds tighter than =");
|
| # (my $b = 1) ?? "true" !! "false";
|
| # is($b, 1, "?? !! just thrown away with = in parens");
|
| };
|
|
|
|
|
| # item assignment
|
|
|
| # XXX this should be a todo, not a skip, but that
|
| # messes up the rest of the file, somehow :(
|
| {
|
| my $c = 1, 2, 3;
|
| is($c, 1, '$ = binds tighter than ,');
|
| my $a = (1, 3) X (2, 4);
|
| is($a, [1, 3], "= binds tighter than X");
|
| }
|
|
|
| # loose unary
|
|
|
| my $x;
|
| is((so $x = 42), 1, "item assignment is tighter than true");
|
|
|
| # comma
|
|
|
| is(((not 1,42)[1]), 42, "not is tighter than comma");
|
|
|
| # list infix
|
|
|
| #?pugs todo 'list infix and assignment'
|
| {
|
| my @d;
|
| ok eval('@d = 1,3 Z 2,4'), "list infix tighter than list assignment, looser t than comma";
|
| is(@d, [1 .. 4], "to complicate things further, it dwims");
|
| }
|
|
|
| {
|
| my @b;
|
| eval('@b = ((1, 3) Z (2, 4))');
|
| is(@b, [1 .. 4], "parens work around this");
|
| };
|
|
|
| # list prefix
|
|
|
| {
|
| my $c;
|
| eval('$c = any 1, 2, Z 3, 4');
|
| #?rakudo skip 'unknown error'
|
| ok($c == 3, "any is less tight than comma and Z");
|
| }
|
|
|
| my @c = 1, 2, 3;
|
| is(@c, [1,2,3], "@ = binds looser than ,");
|
|
|
| # loose and
|
|
|
| {
|
| my $run = 1;
|
| sub isfive (*@args) {
|
| is(@args[0], 5, "First arg is 5, run " ~ $run++);
|
| 1;
|
| }
|
|
|
| # these are two tests per line, actually
|
| # we should have a better way that doesn't just result in
|
| # a wrong plan if gone wrong.
|
| isfive(5) and isfive(5);
|
| isfive 5 and isfive 5;
|
| }
|
|
|
| # loose or
|
|
|
| # terminator
|
|
|
| # Contrary to Perl 5 there are no prototypes, and since normal built-ins
|
| # are not defined as prefix ops, 'uc $a eq $A' actually parses as
|
| # uc($a eq $A), not uc($a) eq $A.
|
| # http://irclog.perlgeek.de/perl6/2009-07-14#i_1316200
|
| #
|
| # so uc(False) stringifies False to '0', and uc('0') is false. Phew.
|
| ok !(uc "a" eq "A"), "uc has the correct precedence in comparision to eq";
|
|
|
Perl 6 has about the same number of precedence levels as Perl 5, but they're differently arranged in spots. Here we list the levels from "tightest" to "loosest", along with a few examples of each level:
A Level Examples
= ===== ========
N Terms 42 3.14 "eek" qq["foo"] $x :!verbose @$array
L Method postfix .meth .+ .? .* .() .[] .{} .<> .«» .:: .= .^ .:
N Autoincrement ++ --
R Exponentiation **
L Symbolic unary ! + - ~ ? | || +^ ~^ ?^ ^
L Multiplicative * / % +& +< +> ~& ~< ~> ?& div mod
L Additive + - +| +^ ~| ~^ ?| ?^
L Replication x xx
X Concatenation ~
X Junctive and &
X Junctive or | ^
L Named unary sleep abs sin temp let
N Structural infix but does <=> leg cmp .. ..^ ^.. ^..^
C Chaining infix != == < <= > >= eq ne lt le gt ge ~~ === eqv !eqv
X Tight and &&
X Tight or || ^^ // min max
R Conditional ?? !! ff fff
R Item assignment = := ::= => += -= **= xx= .=
L Loose unary so not
X Comma operator , :
X List infix Z minmax X X~ X* Xeqv ... E
R List prefix print push say die map substr ... [+] [*] any $ @
X Loose and and andthen
X Loose or or xor orelse
X Sequencer <==, ==>, <<==, ==>>
N Terminator ; {...}, unless, extra ), ], }
Using two ! symbols below generically to represent any pair of operators that have the same precedence, the associativities specified above for binary operators are interpreted as follows:
Assoc Meaning of $a ! $b ! $c
===== =========================
L left ($a ! $b) ! $c
R right $a ! ($b ! $c)
N non ILLEGAL
C chain ($a ! $b) and ($b ! $c)
X list infix:<!>($a; $b; $c)
For unaries this is interpreted as:
Assoc Meaning of !$a!
===== =========================
L left (!$a)!
R right !($a!)
N non ILLEGAL
(In standard Perl there are no unaries that can take advantage of associativity, since at each precedence level the standard operators are either consistently prefix or postfix.)
Note that list associativity (X) only works between identical operators. If two different list-associative operators have the same precedence, they are assumed to be non-associative with respect to each other, and parentheses must be used to disambiguate.
From t/spec/S03-operators/precedence.t lines 209–226: (skip)
-
| # L<S03/Operator precedence/only works between identical operators>
|
|
|
| #?rakudo todo 'list associativity bug'
|
| eval_dies_ok '1, 2 Z 3, 4 X 5, 6',
|
| 'list associativity only works between identical operators';
|
|
|
| {
|
| # Check a 3 != 3 vs 3 !=3 parsing issue that can cropped up in Rakudo.
|
| # Needs careful following of STD to get it right. :-)
|
| my $r;
|
| sub foo($x) { $r = $x }
|
| foo 3 != 3;
|
| is($r, 0, 'sanity 3 != 3');
|
| foo 3 !=3;
|
| is($r, 0, 'ensure 3 !=3 gives same result as 3 != 3');
|
| }
|
|
|
| # vim: ft=perl6
|
For example, the X cross operator and the Z zip operator both have a precedence of "list infix", but:
@a X @b Z @c
is illegal and must be written as either of:
(@a X @b) Z @c
@a X (@b Z @c)
If the only implementation of a list-associative operator is binary, it will be treated as right associative.
The standard precedence levels attempt to be consistent in their associativity, but user-defined operators and precedence levels may mix right and left associative operators at the same precedence level. If two conflicting operators are used ambiguously in the same expression, the operators will be considered non-associative with respect to each other, and parentheses must be used to disambiguate.
If you don't see your favorite operator above, the following sections cover all the operators in precedence order. Basic operator descriptions are here; special topics are covered afterwards.
This isn't really a precedence level, but it's in here because no operator can have tighter precedence than a term. See S02 for longer descriptions of various terms. Here are some examples.
Int literal
42
Num literal
3.14
- Non-interpolating
Str literal
'$100'
- Interpolating
Str literal
"Answer = $answer\n"
- Generalized
Str literal
q["$100"]
qq["$answer"]
- Heredoc
qq:to/END/
Dear $recipient:
Thanks!
Sincerely,
$me
END
- Array composer
[1,2,3]
Provides list context inside. (Technically, it really provides a "semilist" context, which is a semicolon-separated list of statements, each of which is interpreted in list context and then concatenated into the final list.)
- Hash composer
{ }
{ a => 42 }
Inside must be either empty, or a single list starting with a pair or a hash, otherwise you must use hash() or %() instead.
- Closure
{ ... }
When found where a statement is expected, executes immediately. Otherwise always defers evaluation of the inside scope.
- Capture composer
\(@a,$b,%c)
An abstraction representing an argument list that doesn't yet know its context.
- Sigiled variables
$x
@y
%z
$^a
$?FILE
&func
&div:(Int, Int --> Int)
- Sigils as contextualizer functions
$()
@()
%()
&()
- Regexes in quote-like notation
/abc/
rx:i[abc]
s/foo/bar/
- Transliterations
tr/a..z/A..Z/
Note ranges use .. rather than -.
- Type names
Num
::Some::Package
- Subexpressions circumfixed by parentheses
(1+2)
Parentheses are parsed on the inside as a semicolon-separated list of statements, which (unlike the statements in a block) returns the results of all the statements concatenated together as a List of Parcel. How that is subsequently treated depends on its eventual binding.
- Function call with parens:
a(1)
In term position, any identifier followed immediately by a parenthesized expression is always parsed as a term representing a function call even if that identifier also has a prefix meaning, so you never have to worry about precedence in that case. Hence:
not($x) + 1 # means (not $x) + 1
abs($x) + 1 # means (abs $x) + 1
- Pair composers
:limit(5)
:!verbose
- Signature literal
:(Dog $self:)
- Method call with implicit invocant
.meth # call on $_
.=meth # modify $_
Note that this may occur only where a term is expected. Where a postfix is expected, it is a postfix. If only an infix is expected (that is, after a term with intervening whitespace), .meth is a syntax error. (The .=meth form is allowed there only because there is a special .= infix assignment operator that is equivalent in semantics to the method call form but that allows whitespace between the = and the method name.)
- Listop (leftward)
4,3, sort 2,1 # 4,3,1,2
As in Perl 5, a list operator looks like a term to the expression on its left, so it binds tighter than comma on the left but looser than comma on the right--see List prefix precedence below.
All method postfixes start with a dot, though the dot is optional for subscripts. Since these are the tightest standard operator, you can often think of a series of method calls as a single term that merely expresses a complicated name.
See S12 for more discussion of single dispatch method calls.
- Standard single-dispatch method calls
$obj.meth
- Variants of standard single-dispatch method call
$obj.+meth
$obj.?meth
$obj.*meth
In addition to the ordinary . method invocation, there are variants .*, .?, and .+ to control how multiple related methods of the same name are handled.
- Class-qualified method call
$obj.::Class::meth
$obj.Class::meth # same thing, assuming Class is predeclared
As in Perl 5, tells the dispatcher which class to start searching from, not the exact method to call.
- Mutating method call
$obj.=meth
The .= operator does inplace modification of the object on the left.
- Meta-method call
$obj.^meth
The .^ operator calls a class metamethod; foo.^bar is short for foo.HOW.bar.
- Method-like postcircumfixes
$routine.()
$array.[]
$hash.{}
$hash.<>
$hash.«»
The dotless forms of these have exactly the same precedences.
- Dotted form of any other postfix operator
$x.++ # postfix:<++>($x)
- Dotted postfix form of any other prefix operator
$x.:<++> # prefix:<++>($x)
- There is specifically no
infix:<.> operator, so
$foo . $bar
will always result in a compile-time error indicating the user should use infix:<~> instead. This is to catch an error likely to be made by Perl 5 programmers learning Perl 6.
From t/spec/S03-operators/increment.t lines 7–117: (skip)
-
| #L<S03/Autoincrement precedence>
|
|
|
| =begin description
|
|
|
| Verify that autoincrement/autodecrement work properly.
|
| (Overflow cases are handled in S03-operators/overflow.t)
|
|
|
| =end description
|
|
|
| #?rakudo skip 'unimpl Mu++'
|
| my $a = Mu;
|
| is($a++, 0, 'Mu++ == 0');
|
|
|
| $a = Mu;
|
| #?rakudo skip 'unimpl Mu--'
|
| ok(notdef($a--), 'Mu-- is undefined');
|
|
|
| $a = 'x';
|
| is($a++, 'x', 'magical ++ should not be numified');
|
| isa_ok($a, "Str", "it isa Str");
|
|
|
| my %a = ('a' => 1);
|
| %a{"a"}++;
|
| is(%a{'a'}, 2, "hash key");
|
|
|
|
|
| my %b = ('b' => 1);
|
| my $var = 'b';
|
| %b{$var}++;
|
| is(%b{$var}, 2, "hash key via var");
|
|
|
| my @a = (1);
|
| @a[0]++;
|
| is(@a[0], 2, "array elem");
|
|
|
| my @b = (1);
|
| my $moo = 0;
|
| @b[$moo]++;
|
| is(@b[$moo], 2, "array elem via var");
|
| is($moo, 0, "var was not touched");
|
|
|
| # Test that the expression to increment will only be evaluated once.
|
| {
|
| my $was_in_foo;
|
| my sub foo () { $was_in_foo++; 0 };
|
|
|
| my @array = (42);
|
|
|
| is(++@array[+foo()], 43, "++ evaluates the expression to increment only once (1)");
|
| is($was_in_foo, 1, "++ evaluates the expression to increment only once (2)");
|
| }
|
|
|
| # Test case courtesy of Limbic_Region
|
| {
|
| my $curr = 4;
|
| my @array = 1..5;
|
| is @array[$curr], 5, "postincrements in array subscripts work";
|
| @array[ --$curr ]++;
|
|
|
| is $curr, 3, "postincrements in array subscripts work";
|
| is @array[$curr], 5, "postincrements in array subscripts work";
|
| }
|
|
|
| # test incrementing literals
|
| # all of those can be detected at compile time, so use eval_dies_ok here
|
| {
|
| eval_dies_ok ' 4++ ', "can't postincrement a literal number";
|
| eval_dies_ok ' ++4 ', "can't preincrement a literal number";
|
| eval_dies_ok ' 4-- ', "can't postdecrement a literal number";
|
| eval_dies_ok ' --4 ', "can't predecrement a literal number";
|
| eval_dies_ok ' "x"++ ', "can't postincrement a literal string";
|
| eval_dies_ok ' ++"x" ', "can't preincrement a literal string";
|
| eval_dies_ok ' "x"-- ', "can't postdecrement a literal string";
|
| eval_dies_ok ' --"x" ', "can't predecrement a literal string";
|
| }
|
|
|
| # this used to be a rakudo regression
|
| {
|
| my $x = 2.0;
|
| $x += 1;
|
| ok $x == 3.0, 'can add Int to Rat with +=';
|
|
|
| my Rat $y = 2.0;
|
| $y += 1;
|
| ok $y == 3.0, 'can add Int to Rat with += and type constraint';
|
| }
|
|
|
| {
|
| my $x = 2.0.Num;
|
| $x += 1;
|
| ok $x == 3.0, 'can add Int to Num with +=';
|
|
|
| my Num $y = 2.0.Num;
|
| $y += 1;
|
| ok $y == 3.0, 'can add Int to Num with += and type constraint';
|
| }
|
|
|
| # also a Rakudo regression
|
| {
|
| my $x = Bool::False;
|
| is ++$x, Bool::True, '++ on False works';
|
| $x = Bool::False;
|
| is $x.succ, Bool::True, '.succ on False works';
|
|
|
| $x = Bool::True;
|
| is --$x, Bool::False, '-- on True works';
|
| $x = Bool::True;
|
| is $x.pred, Bool::False, '.succ on False works';
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S03-operators/overflow.t lines 7–236: (skip)
-
| #L<S03/Autoincrement precedence>
|
|
|
| =begin description
|
|
|
| Mostly copied from Perl 5.8.4 s t/op/inc.t
|
|
|
| Verify that addition/subtraction properly handle "overflow"
|
| conditions on common architectures. The current tests are
|
| significant on machines with 32-bit longs, but should not
|
| fail anywhere.
|
|
|
| =end description
|
|
|
| my $a = 2147483647;
|
| my $c=$a++;
|
| is($a, 2147483648, "var incremented after post-autoincrement");
|
| is($c, 2147483647, "during post-autoincrement return value is not yet incremented");
|
|
|
| $a = 2147483647;
|
| $c=++$a;
|
| is($a, 2147483648, "var incremented after pre-autoincrement");
|
| is($c, 2147483648, "during pre-autoincrement return value is incremented");
|
|
|
| $a = 2147483647;
|
| $a=$a+1;
|
| is($a, 2147483648, 'simple assignment: $a = $a+1');
|
|
|
| $a = -2147483648;
|
| $c=$a--;
|
| is($a, -2147483649, "var decremented after post-autodecrement");
|
| is($c, -2147483648, "during post-autodecrement return value is not yet decremented");
|
|
|
| $a = -2147483648;
|
| $c=--$a;
|
| is($a, -2147483649, "var decremented after pre-autodecrement");
|
| is($c, -2147483649, "during pre-autodecrement return value is decremented");
|
|
|
| $a = -2147483648;
|
| $a=$a-1;
|
| is($a, -2147483649, 'simple assignment: $a = $a-1');
|
|
|
| $a = 2147483648;
|
| $a = -$a;
|
| $c=$a--;
|
| is($a, -2147483649, "post-decrement negative value");
|
|
|
| $a = 2147483648;
|
| $a = -$a;
|
| $c=--$a;
|
| is($a, -2147483649, "pre-decrement negative value");
|
|
|
| $a = 2147483648;
|
| $a = -$a;
|
| $a=$a-1;
|
| is($a, -2147483649, 'assign $a = -$a; $a = $a-1');
|
|
|
| $a = 2147483648;
|
| my $b = -$a;
|
| $c=$b--;
|
|
|
| is($b, ((-$a)-1), "compare -- to -1 op with same origin var");
|
| is($a, 2147483648, "make sure origin var remains unchanged");
|
|
|
| $a = 2147483648;
|
| $b = -$a;
|
| $c=--$b;
|
| is($b, ((-$a)-1), "same thing with predecremenet");
|
|
|
| $a = 2147483648;
|
| $b = -$a;
|
| $b= $b - 1;
|
| is($b, -(++$a), 'est oder of predecrement in -(++$a)');
|
|
|
| #?rakudo skip "Big number issues with div"
|
| {
|
| is(0x80000000 div 1, 0x80000000, "0x80000000 div 1 == 0x80000000");
|
| is(0x80000000 div -1, -0x80000000, "0x80000000 div -1 == -0x80000000");
|
| is(-0x80000000 div 1, -0x80000000, "-0x80000000 div 1 == -0x80000000");
|
| is(-0x80000000 div -1, 0x80000000, "-0x80000000 div -1 == 0x80000000");
|
| is 18446744073709551616 div 1, 18446744073709551616;
|
| is 18446744073709551616 div 2, 9223372036854775808, "Bignums are not working yet";
|
| is 18446744073709551616 div 4294967296, 4294967296, "Bignums are not working yet";
|
| ok 18446744073709551616 div 9223372036854775808 == 2, '$bignum1 div $bignum2';
|
| }
|
|
|
| # UVs should behave properly
|
| {
|
| is 4063328477 % 65535, 27407;
|
| is 4063328477 % 4063328476, 1;
|
| is 4063328477 % 2031664238, 1;
|
|
|
| is 2031664238 % 4063328477, 2031664238;
|
|
|
| # These should trigger wrapping on 32 bit IVs and UVs
|
|
|
| is 2147483647 + 0, 2147483647;
|
|
|
| # IV + IV promote to UV
|
| is 2147483647 + 1, 2147483648;
|
| is 2147483640 + 10, 2147483650;
|
| is 2147483647 + 2147483647, 4294967294;
|
| # IV + UV promote to NV
|
| is 2147483647 + 2147483649, 4294967296;
|
| # UV + IV promote to NV
|
| is 4294967294 + 2, 4294967296;
|
| # UV + UV promote to NV
|
| is 4294967295 + 4294967295, 8589934590;
|
|
|
| # UV + IV to IV
|
| is 2147483648 + -1, 2147483647;
|
| is 2147483650 + -10, 2147483640;
|
| # IV + UV to IV
|
| is -1 + 2147483648, 2147483647;
|
| is -10 + 4294967294, 4294967284;
|
| # IV + IV to NV
|
| is -2147483648 + -2147483648, -4294967296;
|
| is -2147483640 + -10, -2147483650;
|
| }
|
|
|
| {
|
| tryeq 2147483648 - 0, 2147483648;
|
| tryeq -2147483648 - 0, -2147483648;
|
| tryeq 2000000000 - 4000000000, -2000000000;
|
| }
|
|
|
| # Believe it or not, this one overflows on 32-bit Rakduo as of 3/8/2010.
|
| {
|
| # RT #73262
|
| is_approx 7**(-1), 0.14285714285714, '7**(-1) works';
|
| }
|
|
|
| {
|
| # The peephole optimiser is wrong to think that it can substitute intops
|
| # in place of regular ops, because i_multiply can overflow.
|
| # (Perl 5) Bug reported by "Sisyphus" (kalinabears@hdc.com.au)
|
| my $n = 1127;
|
| my $float = ($n % 1000) * 167772160.0;
|
| tryeq_sloppy $float, 21307064320;
|
|
|
| # On a 32 bit machine, if the i_multiply op is used, you will probably get
|
| # -167772160. It's actually undefined behaviour, so anything may happen.
|
| my $int = ($n % 1000) * 167772160;
|
| tryeq $int, 21307064320;
|
|
|
| }
|
|
|
| {
|
| tryeq -1 - -2147483648, 2147483647;
|
| tryeq 2 - -2147483648, 2147483650;
|
|
|
| tryeq 4294967294 - 3, 4294967291;
|
| tryeq -2147483648 - -1, -2147483647;
|
|
|
| # IV - IV promote to UV
|
| tryeq 2147483647 - -1, 2147483648;
|
| tryeq 2147483647 - -2147483648, 4294967295;
|
| # UV - IV promote to NV
|
| tryeq 4294967294 - -3, 4294967297;
|
| # IV - IV promote to NV
|
| tryeq -2147483648 - +1, -2147483649;
|
| # UV - UV promote to IV
|
| tryeq 2147483648 - 2147483650, -2;
|
| }
|
|
|
| # check with 0xFFFF and 0xFFFF
|
| {
|
| is 65535 * 65535, 4294836225;
|
| is 65535 * -65535, -4294836225;
|
| is -65535 * 65535, -4294836225;
|
| is -65535 * -65535, 4294836225;
|
|
|
| # check with 0xFFFF and 0x10001
|
| is 65535 * 65537, 4294967295;
|
| is 65535 * -65537, -4294967295;
|
| is -65535 * 65537, -4294967295;
|
| is -65535 * -65537, 4294967295;
|
|
|
| # check with 0x10001 and 0xFFFF
|
| is 65537 * 65535, 4294967295;
|
| is 65537 * -65535, -4294967295;
|
| is -65537 * 65535, -4294967295;
|
| is -65537 * -65535, 4294967295;
|
|
|
| # These should all be dones as NVs
|
| is 65537 * 65537, 4295098369;
|
| is 65537 * -65537, -4295098369;
|
| is -65537 * 65537, -4295098369;
|
| is -65537 * -65537, 4295098369;
|
|
|
| # will overflow an IV (in 32-bit)
|
| is 46340 * 46342, 0x80001218;
|
| is 46340 * -46342, -0x80001218;
|
| is -46340 * 46342, -0x80001218;
|
| is -46340 * -46342, 0x80001218;
|
|
|
| is 46342 * 46340, 0x80001218;
|
| is 46342 * -46340, -0x80001218;
|
| is -46342 * 46340, -0x80001218;
|
| is -46342 * -46340, 0x80001218;
|
|
|
| # will overflow a positive IV (in 32-bit)
|
| is 65536 * 32768, 0x80000000;
|
| is 65536 * -32768, -0x80000000;
|
| is -65536 * 32768, -0x80000000;
|
| is -65536 * -32768, 0x80000000;
|
|
|
| is 32768 * 65536, 0x80000000;
|
| is 32768 * -65536, -0x80000000;
|
| is -32768 * 65536, -0x80000000;
|
| is -32768 * -65536, 0x80000000;
|
| }
|
|
|
| #overflow tests from radix.t
|
| {
|
| # some random mad up hex strings (these values are checked against perl5)
|
| is :16("FFACD5FE"), 4289517054, 'got the correct int value from hex FFACD5FE';
|
| is :16("AAA4872D"), 2862909229, 'got the correct int value from hex AAA4872D';
|
| is :16<DEAD_BEEF>, 0xDEADBEEF, 'got the correct int value from hex DEAD_BEEF';
|
|
|
| is(:8<37777777777>, 0xffff_ffff, 'got the correct int value from oct 3777777777');
|
| is +":16<DeAdBeEf>", 0xDEADBEEF, "radix 16 notation works";
|
| is +":16<dead_beef.face>", 0xDEADBEEF + 0xFACE / 65536.0, "fractional base 16 works";
|
|
|
| is( :2<1.1> * 10 ** 10, 15_000_000_000, 'binary number to power of 10' );
|
| is( :2<1.1*10**10>, 15_000_000_000, 'Power of ten in <> works');
|
|
|
| }
|
|
|
|
|
| # vim: ft=perl6
|
From t/spec/S03-operators/autoincrement.t lines 9–51: (skip)
-
| #L<S03/Autoincrement precedence>
|
|
|
| my $base = 10000;
|
|
|
| my $x = 10000;
|
| is(0 + ++$x - 1, $base, '0 + ++$x - 1');
|
| is(0 + $x-- - 1, $base, '0 + $x-- - 1');
|
| is(1 * $x, $base, '1 * $x');
|
| is(0 + $x-- - 0, $base, '0 + $x-- - 0');
|
| is(1 + $x, $base, '1 + $x');
|
| is(1 + $x++, $base, '1 + $x++');
|
| is(0 + $x, $base, '0 + $x');
|
| is(0 + --$x + 1, $base, '0 + --$x + 1');
|
| is(0 + ++$x + 0, $base, '0 + ++$x + 0');
|
| is($x, $base, '$x');
|
|
|
| my @x;
|
| @x[0] = 10000;
|
| is(0 + ++@x[0] - 1, $base, '0 + ++@x[0] - 1');
|
| is(0 + @x[0]-- - 1, $base, '0 + @x[0]-- - 1');
|
| is(1 * @x[0], $base, '1 * @x[0]');
|
| is(0 + @x[0]-- - 0, $base, '0 + @x[0]-- - 0');
|
| is(1 + @x[0], $base, '1 + @x[0]');
|
| is(1 + @x[0]++, $base, '1 + @x[0]++');
|
| is(0 + @x[0], $base, '0 + @x[0]');
|
| is(0 + ++@x[0] - 1, $base, '0 + ++@x[0] - 1');
|
| is(0 + --@x[0] + 0, $base, '0 + --@x[0] + 0');
|
| is(@x[0], $base, '@x[0]');
|
|
|
| my %z;
|
| %z{0} = 10000;
|
| is(0 + ++%z{0} - 1, $base, '0 + ++%z{0} - 1');
|
| is(0 + %z{0}-- - 1, $base, '0 + %z{0}-- - 1');
|
| is(1 * %z{0}, $base, '1 * %z{0}');
|
| is(0 + %z{0}-- - 0, $base, '0 + %z{0}-- - 0');
|
| is(1 + %z{0}, $base, '1 + %z{0}');
|
| is(1 + %z{0}++, $base, '1 + %z{0}++');
|
| is(0 + %z{0}, $base, '0 + %z{0}');
|
| is(0 + ++%z{0} - 1, $base, '0 + ++%z{0} - 1');
|
| is(0 + --%z{0} + 0, $base, '0 + --%z{0} + 0');
|
| is(%z{0}, $base, '%z{0}');
|
|
|
| # Increment of a Str
|
As in C, these operators increment or decrement the object in question either before or after the value is taken from the object, depending on whether it is put before or after. Also as in C, multiple references to a single mutating object in the same expression may result in undefined behavior unless some explicit sequencing operator is interposed. See "Sequence points".
As with all postfix operators in Perl 6, no space is allowed between a term and its postfix. See S02 for why, and for how to work around the restriction with an "unspace".
As mutating methods, all these operators dispatch to the type of the operand and return a result of the same type, but they are legal on value types only if the (immutable) value is stored in a mutable container. However, a bare undefined value (in a suitable Scalar container) is allowed to mutate itself into an Int in order to support the common idiom:
say $x unless %seen{$x}++;
Increment of a Str (in a suitable container) works similarly to Perl 5, but is generalized slightly. A scan is made for the final alphanumeric sequence in the string that is not preceded by a '.' character. Unlike in Perl 5, this alphanumeric sequence need not be anchored to the beginning of the string, nor does it need to begin with an alphabetic character; the final sequence in the string matching <!after '.'> <rangechar>+ is incremented regardless of what comes before it.
From t/spec/S03-operators/autoincrement.t lines 52–177: (skip)
-
| #L<S03/Autoincrement precedence/Increment of a>
|
|
|
| # XXX: these need to be re-examined and extended per changes to S03.
|
| # Also, see the thread at
|
| # http://www.nntp.perl.org/group/perl.perl6.compiler/2007/06/msg1598.html
|
| # which prompted many of the changes to Str autoincrement/autodecrement.
|
|
|
| {
|
| # These are the ranges specified in S03.
|
| # They might be handy for some DDT later.
|
|
|
| my @rangechar = (
|
| [ 'A', 'Z' ],
|
| [ 'a', 'z' ],
|
| [ "\x[391]", "\x[3a9]" ],
|
| [ "\x[3b1]", "\x[3c9]" ],
|
| [ "\x[5d0]", "\x[5ea]" ],
|
|
|
| [ '0', '9' ],
|
| [ "\x[660]", "\x[669]" ],
|
| [ "\x[966]", "\x[96f]" ],
|
| [ "\x[9e6]", "\x[9ef]" ],
|
| [ "\x[a66]", "\x[a6f]" ],
|
| [ "\x[ae6]", "\x[aef]" ],
|
| [ "\x[b66]", "\x[b6f]" ],
|
| );
|
| }
|
|
|
| {
|
| my $x;
|
|
|
| $x = "123.456";
|
| is( ++$x, "124.456", "'123.456'++ is '124.456' (NOT 123.457)" );
|
| $x = "124.456";
|
| is( --$x, "123.456", "'124.456'-- is '123.456'" );
|
| }
|
|
|
| {
|
| my $x;
|
|
|
| $x = "/tmp/pix000.jpg";
|
| is( ++$x, "/tmp/pix001.jpg", "'/tmp/pix000.jpg'++ is '/tmp/pix001.jpg'" );
|
| $x = "/tmp/pix001.jpg";
|
| is( --$x, "/tmp/pix000.jpg", "'/tmp/pix001.jpg'-- is '/tmp/pix000.jpg'" );
|
| }
|
|
|
| {
|
| my $x;
|
|
|
| # EBCDIC check (i and j not contiguous)
|
| $x = "zi";
|
| is( ++$x, "zj", "'zi'++ is 'zj'" );
|
| $x = "zj";
|
| is( --$x, "zi", "'zj'-- is 'zi'" );
|
| $x = "zr";
|
|
|
| # EBCDIC check (r and s not contiguous)
|
| is( ++$x, "zs", "'zr'++ is 'zs'" );
|
| $x = "zs";
|
| is( --$x, "zr", "'zs'-- is 'zr'" );
|
| }
|
|
|
| {
|
| my $foo;
|
|
|
| $foo = 'A00';
|
| ok(--$foo ~~ Failure, "Decrement of 'A00' should fail");
|
|
|
| # TODO: Check that the Failure is "Decrement out of range" and not
|
| # some other unrelated error (for the fail tests above).
|
| }
|
|
|
| {
|
| my $foo;
|
|
|
| $foo = "\x[3a1]";
|
| is( ++$foo, "\x[3a3]", 'there is no \\x[3a2]' );
|
| }
|
|
|
| {
|
| my $foo = "K\x[3c9]";
|
| is( ++$foo, "L\x[3b1]", "increment 'K\x[3c9]'" );
|
| }
|
|
|
| {
|
| my $x;
|
| is ++$x, 1, 'Can autoincrement a Mu variable (prefix)';
|
|
|
| my $y;
|
| $y++;
|
| is $y, 1, 'Can autoincrement a Mu variable (postfix)';
|
| }
|
|
|
| {
|
| class Incrementor {
|
| has $.value;
|
|
|
| method succ() {
|
| Incrementor.new( value => $.value + 42);
|
| }
|
| }
|
|
|
| my $o = Incrementor.new( value => 0 );
|
| $o++;
|
| is $o.value, 42, 'Overriding succ catches postfix increment';
|
| ++$o;
|
| is $o.value, 84, 'Overriding succ catches prefix increment';
|
| }
|
|
|
| {
|
| class Decrementor {
|
| has $.value;
|
|
|
| method pred() {
|
| Decrementor.new( value => $.value - 42);
|
| }
|
| }
|
|
|
| my $o = Decrementor.new( value => 100 );
|
| $o--;
|
| is $o.value, 58, 'Overriding pred catches postfix decrement';
|
| --$o;
|
| is $o.value, 16, 'Overriding pred catches prefix decrement';
|
| }
|
|
|
| {
|
From t/spec/S03-operators/autoincrement.t lines 178–196: (skip)
-
| # L<S03/Autoincrement precedence/Increment of a>
|
|
|
| my $x = "b";
|
| is $x.succ, 'c', '.succ for Str';
|
| is $x.pred, 'a', '.pred for Str';
|
|
|
| my $y = 1;
|
| is $y.succ, 2, '.succ for Int';
|
| is $y.pred, 0, '.pred for Int';
|
|
|
| my $z = Num.new();
|
| is $z.succ, 1 , '.succ for Num';
|
| is $z.pred, -1, '.pred for Num'
|
| }
|
|
|
| # RT #63644
|
| eval_dies_ok 'my $a; $a++ ++;', 'parse error for "$a++ ++"';
|
|
|
| # vim: ft=perl6
|
The <rangechar> character class is defined as that subset of characters that Perl knows how to increment within a range, as defined below.
The additional matching behaviors provide two useful benefits: for its typical use of incrementing a filename, you don't have to worry about the path name or the extension:
$file = "/tmp/pix000.jpg";
$file++; # /tmp/pix001.jpg, not /tmp/pix000.jph
Perhaps more to the point, if you happen to increment a string that ends with a decimal number, it's likely to do the right thing:
$num = "123.456";
$num++; # 124.456, not 123.457
Character positions are incremented within their natural range for any Unicode range that is deemed to represent the digits 0..9 or that is deemed to be a complete cyclical alphabet for (one case of) a (Unicode) script. Only scripts that represent their alphabet in codepoints that form a cycle independent of other alphabets may be so used. (This specification defers to the users of such a script for determining the proper cycle of letters.) We arbitrarily define the ASCII alphabet not to intersect with other scripts that make use of characters in that range, but alphabets that intersperse ASCII letters are not allowed.
If the current character in a string position is the final character in such a range, it wraps to the first character of the range and sends a "carry" to the position left of it, and that position is then incremented in its own range. If and only if the leftmost position is exhausted in its range, an additional character of the same range is inserted to hold the carry in the same fashion as Perl 5, so incrementing '(zz99)' turns into '(aaa00)' and incrementing '(99zz)' turns into '(100aa)'.
The following Unicode ranges are some of the possible rangechar ranges. For alphabets we might have ranges like:
A..Z # ASCII uc
a..z # ASCII lc
Α..Ω # Greek uc
α..ω # Greek lc (presumably skipping C<U+03C2>, final sigma)
א..ת # Hebrew
etc. # (XXX out of my depth here)
For digits we have ranges like:
0..9 # ASCII
٠..٩ # Arabic-Indic
०..९ # Devangari
০..৯ # Bengali
੦..੯ # Gurmukhi
૦..૯ # Gujarati
୦..୯ # Oriya
etc.
Other non-script 0..9 ranges may also be incremented, such as
⁰..⁹ # superscripts (note, cycle includes latin-1 chars)
₀..₉ # subscripts
0..9 # fullwidth digits
Conjecturally, any common sequence may be treated as a cycle even if it does not represent 0..9:
Ⅰ..Ⅻ # clock roman numerals uc
ⅰ..ⅻ # clock roman numerals lc
①..⑳ # circled digits 1..20
⒜..⒵ # parenthesize lc
⚀..⚅ # die faces 1..6
❶..❿ # dingbat negative circled 1..10
etc.
While it doesn't really make sense to "carry" such numbers when they reach the end of their cycle, treating such values as incrementable may be convenient for writing outlines and similar numbered bullet items. (Note that we can't just increment unrecognized characters, because we have to locate the string's final sequence of rangechars before knowing which portion of the string to increment. Note also that all character increments can be handled by lookup in a single table of successors since we've defined our ranges not to include overlapping cycles.)
Perl 6 also supports Str decrement with similar semantics, simply by running the cycles the other direction. However, leftmost characters are never removed, and the decrement fails when you reach a string like "aaa" or "000".
Increment and decrement on non-Str types are defined in terms of the .succ and .pred methods on the type of object in the Scalar container. More specifically,
++$var
--$var
are equivalent to
$var.=succ
$var.=pred
If the type does not support these methods, the corresponding increment or decrement operation will fail. (The optimizer is allowed to assume that the ordinary increment and decrement operations on integers will not be overridden.)
Increment of a Bool (in a suitable container) turns it true. Decrement turns it false regardless of how many times it was previously incremented. This is useful if your %seen hash is actually a KeySet, in which case decrement actually deletes it from the KeySet.
- Autoincrement
prefix:<++> or postfix:<++> operator
$x++
++$x;
- Autodecrement
prefix:<--> or postfix:<--> operator
$x--
--$x
infix:<**> exponentiation operator
$x ** 2
Unless the right argument is a non-negative integer the result is likely to be an approximation. If the right argument is of an integer type, exponentiation is at least as accurate as repeated multiplication on the left side's type. (From which it can be deduced that Int**UInt is always exact, since Int supports arbitrary precision.) If the right argument is an integer represented in a non-integer type, the accuracy is left to implementation provided by that type; there is no requirement to recognize an integer to give it special treatment.
prefix:<?>, boolean context
?$x
Evaluates the expression as a boolean and returns True if expression is true or False otherwise. See "so" below for a low-precedence alternative.
prefix:<!>, boolean negation
From t/spec/S03-operators/context-forcers.t lines 153–201: (skip)
-
| # L<S03/Symbolic unary precedence/"prefix:<!>">
|
| {
|
| my $a = '';
|
| isa_ok(!$a, Bool, 'it is forced into a Bool');
|
| ok(!$a, 'it is forced into boolean context');
|
|
|
| my $b = 'This will be true';
|
| isa_ok(!$b, Bool, 'it is forced into a Bool');
|
| ok(!(!$b), 'it is forced into boolean context');
|
|
|
| my $c = 0;
|
| isa_ok(!$c, Bool, 'it is forced into a Bool');
|
| ok(!$c, 'it is forced into boolean context');
|
|
|
| my $d = 1;
|
| isa_ok(!$d, Bool, 'it is forced into a Bool');
|
| ok(!(!$d), 'it is forced into boolean context');
|
|
|
| }
|
| #?rakudo skip 'is context'
|
| {
|
| my $arrayref is context = list(1,2,3);
|
|
|
| ok eval_elsewhere('!(!(@$+arrayref))'), '!(@$arrayref) syntax works';
|
| ok eval_elsewhere('!(!(@($+arrayref)))'), '!(@($arrayref)) syntax works';
|
| }
|
|
|
| # int context
|
| # tested in t/spec/S32-num/int.t
|
|
|
| #?rakudo skip 'TODO: @(), list assignment'
|
| {
|
| my $x = [0, 100, 280, 33, 400, 5665];
|
|
|
| is (@($x)[1], 100, '@$x works');
|
|
|
| is (@($x)[3]+50, 83, '@$x works inside a larger expression');
|
|
|
| my $y = [601, 700, 888];
|
|
|
| my @total = (@$x, @$y);
|
|
|
| is (@total[0], 0, "total[0] is 0");
|
| is (@total[1], 100, "total[1] is 100");
|
| is (@total[6], 601, "total[1] is 100");
|
| is (@total[8], 888, "total[1] is 100");
|
| }
|
|
|
| # the "upto" operator
|
!$x
Returns the opposite of what ? would. See "not" below for a low-precedence alternative.
prefix:<+>, numeric context
+$x
Unlike in Perl 5, where + is a no-op, this operator coerces to numeric context in Perl 6. (It coerces only the value, not the original variable.) For values that do not already do the Numeric role, the narrowest appropriate type of Int, Rat, Num, or Complex will be returned; however, string containing two integers separated by a / will be returned as a Rat (or a FatRat if the denominator overflows an int64). Exponential notation and radix notations are recognized.
prefix:<->, numeric negation
From t/spec/S03-operators/context-forcers.t lines 94–106: (skip)
-
| # L<S03/Symbolic unary precedence/"prefix:<->">
|
| {
|
| my $a = '2 is my favorite number';
|
| ok(-$a ~~ Numeric, 'it is forced into a Num');
|
| is(-$a, -2, 'forced into numeric context');
|
|
|
| my $b = 'Did you know that, 2 is my favorite number';
|
| ok(-$b ~~ Numeric, 'it is forced into a Num');
|
|
|
| # doubly-negated because -0 === 0 isn't neccessarily true
|
| is(-(-$b), 0, 'non numbers forced into numeric context are 0');
|
| }
|
|
|
-$x
Coerces to numeric and returns the arithmetic negation of the resulting number.
prefix:<~>, string context
~$x
Coerces the value to a string, if it does not already do the Stringy role. (It only coerces the value, not the original variable.) As with numerics, it is guaranteed only to coerce to something Stringy, not necessarily Str.
prefix:<|>, flatten object into arglist
| $capture
Interpolates the contents of the Capture (or Capture-like) value into the current argument list as if they had been specified literally. If the first argument of the capture is marked as an invocant but is used in a context not expecting one, it is treated as an ordinary positional argument.
prefix:<||>, flatten object into semicolon list
|| $parcel
Interpolates the elements of the Parcel (or any other ordered value) into the current argument list as if they had been specified literally, separated by semicolons, that is, at the multi-dimensional level. It is an error to use this operator outside of a slice context; in other words it must be bound into a ** (slice) parameter rather than a * (slurpy) parameter.
prefix:<+^>, numeric bitwise negation
+^$x
Coerces to Int and then does bitwise negation on the number, returning an Int. (In order not to have to represent an infinitude of 1's, represents that value as some negative in 2's complement form.)
prefix:<~^>, string bitwise negation
~^$x
Coerces NFG strings to non-variable-encoding string buffer type (such as buf8, buf16, or buf32) and then does negation (complement) on each bit of each integer element, returning a buffer of the same size as the input.
The use of coercion probably indicates a design error, however. This operator is distinguished from numeric bitwise negation in order to provide bit vectors that extend on the right rather than the left (and always do unsigned extension).
prefix:<?^>, boolean negation
?^$x
Coerces to boolean and then flips the bit. (Same as !.)
prefix:<^>, upto operator
From t/spec/S03-operators/context-forcers.t lines 202–225: (skip)
-
| # L<S03/Symbolic unary precedence/"prefix:<^>">
|
|
|
| # ^$x is the range 0 .. ($x -1)
|
| {
|
| ok 0 ~~ ^10, '0 is in ^10';
|
| ok 9 ~~ ^10, '9 is in ^10';
|
| ok 9.9 ~~ ^10, '9.99 is in ^10';
|
| ok 10 !~~ ^10, '10 is not in ^10';
|
| is (^10).elems, 10, '^10 has 10 elems';
|
| isa_ok ^10, Range;
|
|
|
| # now the same for ^@array, in which case prefix:<^>
|
| # imposes numeric context
|
|
|
| my @a = <one two three four five six seven eight nine ten>;
|
| ok 0 ~~ ^@a, '0 is in ^10';
|
| ok 9 ~~ ^@a, '9 is in ^10';
|
| ok 9.9 ~~ ^@a, '9.99 is in ^10';
|
| ok 10 !~~ ^@a, '10 is not in ^10';
|
| is (^@a).elems, 10, '^10 has 10 elems';
|
| isa_ok ^@a, Range;
|
| }
|
|
|
| # vim: ft=perl6
|
^$limit
Constructs a range of 0 ..^ +$limit or locates a metaclass as a shortcut for $limit.HOW. See "Range and RangeIter semantics".
infix:<*>
$x*$y
Multiplication, resulting in wider type of the two.
infix:</>
$numerator / $denominator
Performs division of real or complex numbers, returning a real or complex number of appropriate type.
If both operands are of integer type, the operator returns the corresponding Rat value.
Otherwise, if either operand is of Complex type, converts both operands to Complex and does division returning Complex.
Otherwise, if either operand is of Num type, converts both operands to Num and does division returning Num. If the denominator is zero, returns an object representing either +Inf, NaN, or -Inf as the numerator is positive, zero, or negative. (This is construed as the best default in light of the operator's possible use within hyperoperators and junctions. Note however that these are not actually the native IEEE non-numbers; they are undefined values of the "unthrown exception" type that happen to represent the corresponding IEEE concepts, and if you subsequently try to use one of these values in a non-parallel computation, it will likely throw an exception at that point.)
infix:<div>, integer division
$numerator div $denominator
Dispatches to the infix:<div> multi most appropriate to the operand types, returning a value of the same type. Not coercive, so fails on differing types.
Policy on what to do about division by zero is up to the type, but for the sake of hyperoperators and junctions those types that can represent overflow (or that can contain an unthrown exception) should try to do so rather than simply throwing an exception. (And in general, other operators that might fail should also consider their use in hyperops and junctions, and whether they can profitably benefit from a lazy exception model.)
In general, div should give the same result as
$x div $y == floor($x/$y);
but the return value should be the same type as $x.
infix:<%>, modulo
$x % $y
If necessary, coerces non-numeric arguments to an appropriate Numeric type, then calculates the remainder, which is defines as:
$x % $y == $x - floor($x / $y) * $y
infix:<mod>, integer modulo
$x mod $y
Dispatches to the infix:<mod> multi most appropriate to the operand types, returning a value of the same type. Not coercive, so fails on differing types.
This should preserve the identity
$x mod $y == $x - ($x div $y) * $y
infix:['+&'], numeric bitwise and
$x +& $y
Converts both arguments to Int and does a boolean AND between corresponding bits of each integer, returning an Int result.
infix:['+<'], numeric shift left
$integer +< $bits
infix:['+>'], numeric shift right
$integer +> $bits
By default, signed types do sign extension, while unsigned types do not, but this may be enabled or disabled with a :signed or :!signed adverb.
infix:<~&>, buffer bitwise and
$x ~& $y
Coerces NFG strings to non-variable-encoding string buffer type (such as buf8, buf16, or buf32) and then does numeric bitwise AND on corresponding integers of the two buffers, logically padding the shorter buffer with 0 values. returning a buffer sufficiently large to contain all non-zero integer results (which for AND is at most the size of the shorter of the two buffers).
The use of coercion probably indicates a design error, however. This operator is distinguished from numeric bitwise AND in order to provide bit vectors that extend on the right rather than the left (and always do unsigned extension).
infix:['~<'], buffer bitwise shift left
$buf ~< $bits
infix:['~>'], buffer bitwise shift right
$buf ~> $bits
Sign extension is not done by default but may be enabled with a :signed adverb.
infix:<?&>, boolean and
$x ?& $y
Converts both arguments to type Bool and then ANDs those, returning the resulting Bool.
Any bit shift operator may be turned into a rotate operator with the :rotate adverb. If :rotate is specified, the concept of sign extension is meaningless, and you may not specify a :signed adverb.
infix:<+>, numeric addition
$x + $y
Microeditorial: As with most of these operators, any coercion or type mismatch is actually handled by multiple dispatch. The intent is that all such variants preserve the notion of numeric addition to produce a numeric result, presumably stored in suitably "large" numeric type to hold the result. Do not overload the + operator for other purposes, such as concatenation. (And please do not overload the bitshift operators to do I/O.) In general we feel it is much better for you to make up a different operator than overload an existing operator for "off topic" uses. All of Unicode is available for this purpose.
infix:<->, numeric subtraction
$x - $y
infix:<+|>, numeric bitwise inclusive or
$x +| $y
Converts both arguments to Int and does a boolean OR between corresponding bits of each integer, returning an Int result.
infix:<+^> numeric bitwise exclusive or
$x +^ $y
Converts both arguments to Int and does a boolean XOR between corresponding bits of each integer, returning an Int result.
infix:<~|>, buffer bitwise inclusive or
$x ~| $y
Coerces NFG strings to non-variable-encoding string buffer type (such as buf8, buf16, or buf32) and then does numeric bitwise OR on corresponding integers of the two buffers, logically padding the shorter buffer with 0 values. returning a buffer sufficiently large to contain all non-zero integer results (which for OR is at most the size of the longer of the two buffers).
The use of coercion probably indicates a design error, however. This operator is distinguished from numeric bitwise OR in order to provide bit vectors that extend on the right rather than the left (and always do unsigned extension).
infix:<~^> buffer bitwise exclusive or
$x ~^ $y
Coerces NFG strings to non-variable-encoding string buffer type (such as buf8, buf16, or buf32) and then does numeric bitwise XOR on corresponding integers of the two buffers, logically padding the shorter buffer with 0 values. returning a buffer sufficiently large to contain all non-zero integer results (which for XOR is at most the size of the longer of the two buffers).
The use of coercion probably indicates a design error, however. This operator is distinguished from numeric bitwise XOR in order to provide bit vectors that extend on the right rather than the left (and always do unsigned extension).
infix:<?|>, boolean inclusive or
$x ?| $y
Converts both arguments to type Bool and then ORs those, returning the resulting Bool.
infix:<?^> boolean exclusive or
$x ?^ $y
Converts both arguments to type Bool and then XORs those, returning the resulting Bool.
infix:<x>, string/buffer replication
$string x $count
Evaluates the left argument in string context, replicates the resulting string value the number of times specified by the right argument and returns the result as a single concatenated string regardless of context.
If the count is less than 1, returns the null string. The count may not be * because Perl 6 does not support infinite strings. (At least, not yet...) Note, however, that an infinite string may someday be emulated with cat($string xx *), in which case $string x * may be a shorthand for that.
infix:<xx>, list replication
@list xx $count
Evaluates the left argument in list context, replicates the resulting Parcel value the number of times specified by the right argument and returns the result as a list of Parcels (which will behave differently depending on whether it's bound into a flat context or a slice context).
If the count is less than 1, returns the empty list, (). If the count is *, returns an infinite list (lazily, since lists are lazy by default).
From t/spec/S03-operators/misc.t lines 17–19: (skip)
-
| # L<S03/Concatenation>
|
| is($str3, $str4, "~");
|
|
|
From t/spec/S03-operators/misc.t lines 49–53: (skip)
-
| # L<S03/Concatenation>
|
| is("text " ~ "stitching", "text stitching", 'concatenation with ~ operator');
|
|
|
| # Bit Stitching
|
|
|
infix:<~>, string/buffer concatenation
$x ~ $y
infix:<&>, all() operator
From t/spec/S03-operators/also.t lines 4–22: (skip)
-
| # L<S03/"Junctive and (all) precedence"/"infix:<&>">
|
|
|
| ok ?(1 S& 2), "basic infix:<S&>";
|
| #?rakudo skip "multiple S& NYI"
|
| ok ?(1 S& 2 S& 3), "basic infix:<S&> (multiple S&'s)";
|
| ok !(0 S& 1), "S& has and-semantics (first term 0)";
|
| ok !(1 S& 0), "also has and-semantics (second term 0)";
|
|
|
| my $x = '';
|
|
|
| ok ?('a' ~~ { $x ~= "b"; True } S& { $x ~= "c"; True }), 'S& with two blocks';
|
| is $x, 'bc', 'blocks called in the right order';
|
|
|
| my $executed = 0;
|
|
|
| ok !('a' ~~ 'b' S& { $executed = 1; True }), 'and semantics';
|
| ok !$executed, 'short-circuit';
|
|
|
| # vim: ft=perl6
|
$a & $b & $c ...
By default junctions are allowed to reorder the comparisons in any order that makes sense to the optimizer. To suppress this, use the S metaoperator for force sequential evaluation, which will construct a list of ANDed patterns with the same semantics as infix:<&>, but with left-to-right evaluation guaranteed, for use in guarded patterns:
$target ~~ MyType S& *.mytest1 S& *.mytest2
This is useful when later tests might throw exceptions if earlier tests don't pass. This cannot be guaranteed by:
$target ~~ MyType & *.mytest1 & *.mytest2
infix:<|>, any() operator
$a | $b | $c ...
- By default junctions are allowed to reorder the comparisons in any order that makes sense to the optimizer. To suppress this, use the
S metaoperator for force sequential evaluation, which will construct a list of ORed patterns with the same semantics as infix:<|>, but with left-to-right evaluation guaranteed, for use in guarded patterns where the left argument is much more easily falsifiable than the right:
$target ~~ *.mycheaptest S| *.myexpensivetest
This is also useful when you want to perform tests in order of safety:
$target ~~ MyType S| *.mysafetest S| *.mydangeroustest
infix:<^>, one() operator
$a ^ $b ^ $c ...
The S^ variant guarantees left-to-right evaluation, and in boolean context short-circuits to false if it sees a second match.
Functions of one argument
sleep
abs
sin
... # see S29 Functions
Note that, unlike in Perl 5, you must use the .meth forms to default to $_ in Perl 6.
There is no unary rand prefix in Perl 6, though there is a .rand method call and an argumentless rand term. There is no unary int prefix either; you must use a typecast to a type such as Int or int. (Typecasts require parentheses and may not be used as prefix operators.) In other words:
my $i = int $x; # ILLEGAL
From t/spec/S03-operators/precedence.t lines 199–208: (skip)
-
| # L<S03/Named unary precedence/my $i = int $x; # ILLEGAL>
|
|
|
| eval_dies_ok 'int 4.5', 'there is no more prefix:<int>';
|
|
|
|
|
| # http://irclog.perlgeek.de/perl6/2009-07-14#i_1315249
|
| ok ((1 => 2 => 3).key !~~ Pair), '=> is right-assoc (1)';
|
| ok ((1 => 2 => 3).value ~~ Pair), '=> is right-assoc (2)';
|
|
|
|
|
is a syntax error (two terms in a row), because int is a type name now.
prefix:<int>
Deprecated, use the Int() coercion or the floor function.
prefix:<sleep>
Coerces to an appropriate Real type, then suspends the current thread of execution for the specified number of seconds, which may be fractional. Remember that although a Rat is capable of attosecond precision, your computer is probably not capable of attosecond accuracy.
prefix:<abs>
Returns the absolute value of the specified argument.
infix:<but>
$value but Mixin
infix:<does>
$object does Mixin
- Sort comparisons
From t/spec/S03-operators/spaceship.t lines 5–30: (skip)
-
| # L<S03/Nonchaining binary precedence/Sort comparisons>
|
|
|
| plan 5;
|
|
|
| my %ball = map {; $_ => 1 }, 1..12;
|
| is(
|
| (%ball{12}) <=> (%ball{11}),
|
| 0,
|
| 'parens with spaceship parse incorrectly',
|
| );
|
|
|
| my $result_1 = ([+] %ball{10..12}) <=> ([+] %ball{1..3});
|
|
|
| is($result_1, 0, 'When spaceship terms are non-trivial members it parses incorrectly');
|
| my $result_2 = ([+] %ball{11,12}) <=> ([+] %ball{1,2});
|
| is($result_2, 0, 'When spaceship terms are non-trivial members it parses incorrectly');
|
| #?rakudo skip 'unspecced: does infix:«<=>» numify its arguments?'
|
| {
|
| my $result_3 = ([0] <=> [0,1]);
|
| is($result_3, -1, 'When spaceship terms are non-trivial members it parses incorrectly');
|
| }
|
|
|
| %ball{12} = 0.5;
|
| is(%ball{12} <=> %ball{11}, -1, 'When spaceship terms are non-integral numbers it parses incorrectly');
|
|
|
| # vim: ft=perl6
|
$num1 <=> $num2
$str1 leg $str2
$obj1 cmp $obj2
These operators compare their operands using numeric, string, or eqv semantics respectively, and depending on the order return one of Order::Increase, Order::Same, or Order::Decrease (which numerify to -1, 0, or +1). See "Comparison semantics".
From t/spec/S03-operators/comparison.t lines 8–12: (skip)
-
| # L<S03/Nonchaining binary precedence/Order::Increase, Order::Same, or Order::Decrease>
|
| is(+Order::Increase, -1, 'Order::Increase numifies to -1');
|
| is(+Order::Same, 0, 'Order::Same numifies to 0');
|
| is(+Order::Decrease, 1, 'Order::Decrease numifies to 1');
|
|
|
- Range object constructor
From t/spec/S03-operators/range.t lines 8–35: (skip)
-
| # L<S03/Nonchaining binary precedence/Range object constructor>
|
|
|
| # 3..2 must *not* produce "3 2". Use reverse to get a reversed range. -lwall
|
|
|
| is ~(3..6), "3 4 5 6", "(..) works on numbers (1)";
|
| is ~(3..3), "3", "(..) works on numbers (2)";
|
| is ~(3..2), "", "(..) works on auto-rev numbers (3)";
|
| is ~(8..11), "8 9 10 11", "(..) works on carried numbers (3)";
|
|
|
| is ~("a".."c"), "a b c", "(..) works on chars (1)";
|
| is ~("a".."a"), "a", "(..) works on chars (2)";
|
| is ~("b".."a"), "", "(..) works on chars (3)";
|
| is ~("a".."z"), "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", "(..) works on char range ending in z";
|
| is ~("A".."Z"), "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", "(..) works on char range ending in Z";
|
| is ~("Y".."AB"), "", "(..) works on carried chars (3)";
|
|
|
| #?rakudo 4 skip 'Spec under design here'
|
| is ~('Y'..'z'), 'Y Z', '(..) works on uppercase letter .. lowercase letter (1)';
|
| is ~('z'..'Y'), '', '(..) works on auto-rev uppercase letter .. lowercase letter (2)';
|
| is ~('Y'..'_'), 'Y Z', '(..) works on letter .. non-letter (1)';
|
| is ~('_'..'Y'), '', '(..) works on auto-rev letter .. non-letter (2)';
|
|
|
| is ~(3..9-3), "3 4 5 6", "(..) has correct precedence (1)";
|
| is ~(5..9-5), "", "(..) has correct precedence (2)";
|
| is ~(2+1..6), "3 4 5 6", "(..) has correct precedence (3)";
|
| is ~(2+5..6), "", "(..) has correct precedence (4)";
|
|
|
| # Test the three exclusive range operators:
|
$min .. $max
$min ^.. $max
$min ..^ $max
$min ^..^ $max
Constructs Range objects, optionally excluding one or both endpoints. See "Range and RangeIter semantics".
From t/spec/S03-operators/equality.t lines 10–42: (skip)
-
| #L<S03/Chaining binary precedence>
|
| #L<S03/Comparison semantics>
|
|
|
| # string equality & inequality
|
| ok("a" eq "a", "eq true");
|
| ok(!("a" eq "ab"), "eq false");
|
| ok("a" ne "ab", "ne true");
|
| ok(!("a" ne "a"), "ne false");
|
|
|
| # potential problem cases
|
| ok("\0" eq "\0", "eq on strings with null chars");
|
| ok(!("\0" eq "\0\0"), "!eq on strings with null chars but different lengths");
|
| ok(!("a" eq "a\0"), "eq doesn't have null-padding semantics");
|
| ok(!("a" eq "a "), "eq doesn't have space-padding semantics");
|
| ok("a" ne "a\0", "ne doesn't have null-padding semantics");
|
| ok("a" ne "a ", "ne doesn't have space-padding semantics");
|
|
|
| # string context on undefined values
|
| my $foo;
|
| #?rakudo todo "+Any() doesn't work yet"
|
| ok($foo eq "", "Any eq ''");
|
| ok($foo ne "f", "Any ne 'f'");
|
|
|
| my @foo;
|
| ok(@foo[0] eq "", "Array Any eq ''");
|
| ok(@foo[0] ne "f", "Array Any ne 'f'");
|
|
|
| # numeric equality & inequality
|
| ok(2 == 2, "== true");
|
| ok(!(2 == 3), "== false");
|
| ok(2 != 3, "!= true");
|
| ok(!(2 != 2), "!= false");
|
|
|
From t/spec/S03-operators/relational.t lines 9–103: (skip)
-
| #L<S03/Chaining binary precedence>
|
|
|
| # from t/operators/relational.t
|
|
|
| ## numeric relationals ( < , >, <=, >= )
|
|
|
| ok(1 < 2, '1 is less than 2');
|
| ok(!(2 < 1), '2 is ~not~ less than 1');
|
|
|
| ok 1/4 < 3/4, '1/4 is less than 3/4';
|
| ok !(3/4 < 1/4), '3/4 is not less than 1/4';
|
| ok 1/2 < 1, '1/2 is less than 1';
|
| ok !(1 < 1/2), '1 is not less than 1/2';
|
|
|
| ok(2 > 1, '2 is greater than 1');
|
| ok(!(1 > 2), '1 is ~not~ greater than 2');
|
|
|
| ok 3/4 > 1/4, '3/4 is greater than 1/4';
|
| ok !(1/4 > 3/4), '1/2 is not greater than 3/4';
|
| ok 1 > 1/2, '1 is greater than 1/2';
|
| ok !(1/2 > 1), '1/2 is not greater than 1';
|
|
|
| ok(1 <= 2, '1 is less than or equal to 2');
|
| ok(1 <= 1, '1 is less than or equal to 1');
|
| ok(!(1 <= 0), '1 is ~not~ less than or equal to 0');
|
|
|
| ok 1/4 <= 3/4, '1/4 is less than or equal to 3/4';
|
| ok !(3/4 <= 1/4), '3/4 is not less than or equal to 1/4';
|
| ok 1/2 <= 1, '1/2 is less than or equal to 1';
|
| ok !(1 <= 1/2), '1 is not less than or equal to 1/2';
|
| ok 1/2 <= 1/2, '1/2 is less than or equal to 1/2';
|
|
|
| ok(2 >= 1, '2 is greater than or equal to 1');
|
| ok(2 >= 2, '2 is greater than or equal to 2');
|
| ok(!(2 >= 3), '2 is ~not~ greater than or equal to 3');
|
|
|
| ok !(1/4 >= 3/4), '1/4 is greater than or equal to 3/4';
|
| ok 3/4 >= 1/4, '3/4 is not greater than or equal to 1/4';
|
| ok !(1/2 >= 1), '1/2 is greater than or equal to 1';
|
| ok 1 >= 1/2, '1 is not greater than or equal to 1/2';
|
| ok 1/2 >= 1/2, '1/2 is greater than or equal to 1/2';
|
|
|
| # +'a' is 0. This means 1 is less than 'a' in numeric context but not string
|
| ok('a' < '1', '< uses numeric context');
|
| ok('a' <= '1', '<= uses numeric context (1)');
|
| ok('a' <= '0', '<= uses numeric context (2)');
|
| ok(!('a' > '1'), '> uses numeric context');
|
| ok(!('a' >= '1'), '>= uses numeric context (1)');
|
| ok(('a' >= '0'), '>= uses numeric context (2)');
|
|
|
| # Ensure that these operators actually return Bool::True or Bool::False
|
| is(1 < 2, Bool::True, '< true');
|
| is(1 > 0, Bool::True, '> true');
|
| is(1 <= 2, Bool::True, '<= true');
|
| is(1 >= 0, Bool::True, '>= true');
|
| is(1 < 0, Bool::False, '< false');
|
| is(1 > 2, Bool::False, '> false');
|
| is(1 <= 0, Bool::False, '<= false');
|
| is(1 >= 2, Bool::False, '>= false');
|
|
|
| ## string relationals ( lt, gt, le, ge )
|
|
|
| ok('a' lt 'b', 'a is less than b');
|
| ok(!('b' lt 'a'), 'b is ~not~ less than a');
|
|
|
| ok('b' gt 'a', 'b is greater than a');
|
| ok(!('a' gt 'b'), 'a is ~not~ greater than b');
|
|
|
| ok('a' le 'b', 'a is less than or equal to b');
|
| ok('a' le 'a', 'a is less than or equal to a');
|
| ok(!('b' le 'a'), 'b is ~not~ less than or equal to a');
|
|
|
| ok('b' ge 'a', 'b is greater than or equal to a');
|
| ok('b' ge 'b', 'b is greater than or equal to b');
|
| ok(!('b' ge 'c'), 'b is ~not~ greater than or equal to c');
|
|
|
| # +'a' is 0. This means 1 is less than 'a' in numeric context but not string
|
| ok(!('a' lt '1'), 'lt uses string context');
|
| ok(!('a' le '1'), 'le uses string context (1)');
|
| ok(!('a' le '0'), 'le uses string context (2)');
|
| ok('a' gt '1', 'gt uses string context');
|
| ok('a' ge '1', 'ge uses string context (1)');
|
| ok('a' ge '0', 'ge uses string context (2)');
|
|
|
| # Ensure that these operators actually return Bool::True or Bool::False
|
| is('b' lt 'c', Bool::True, 'lt true');
|
| is('b' gt 'a', Bool::True, 'gt true');
|
| is('b' le 'c', Bool::True, 'le true');
|
| is('b' ge 'a', Bool::True, 'ge true');
|
| is('b' lt 'a', Bool::False, 'lt false');
|
| is('b' gt 'c', Bool::False, 'gt false');
|
| is('b' le 'a', Bool::False, 'le false');
|
| is('b' ge 'c', Bool::False, 'ge false');
|
|
|
| ## Multiway comparisons (RFC 025)
|
All operators on this precedence level may be chained; see "Chained comparisons". They all return a boolean value.
infix:<==> etc.
From t/spec/S03-operators/misc.t lines 26–48: (skip)
-
| # L<S03/Chaining binary precedence/==>
|
| my $five = 5;
|
| my $four = 4;
|
| my $wibble = 4;
|
|
|
| ok(!($five == $four), "== (false)");
|
| ok($wibble == $four, "== (true)");
|
| ok(!($wibble != $four), "== (false)");
|
| ok($five != $four, "!= (true)");
|
|
|
| ok($five == 5, "== (const on rhs)");
|
| ok(!($five != 5), "!= (const on rhs)");
|
|
|
| ok(5 == $five, "== (const on lhs)");
|
| ok(!(5 != $five), "!= (const on lhs)");
|
|
|
| ok($five == (2 + 3), "== (sum on rhs)");
|
| ok(!($five != (2 + 3)), "== (sum on rhs)");
|
|
|
| is(2 + 3, $five, "== (sum on lhs)");
|
| ok((2 + 3) == 5, "== (sum on lhs)");
|
| ok(!((2 + 3) != $five), "== (sum on lhs)");
|
|
|
== != < <= > >=
As in Perl 5, converts to Num before comparison. != is short for !==.
infix:<eq> etc.
eq ne lt le gt ge
As in Perl 5, converts to Str before comparison. ne is short for !eq.
- Generic ordering
$a before $b
$a after $b
- Smart match
$obj ~~ $pattern
Perl 5's =~ becomes the "smart match" operator ~~, with an extended set of semantics. See "Smart matching" for details.
To catch "brainos", the Perl 6 parser defines an infix:<=~> operator which always fails at compile time with a message directing the user to use ~~ or ~= (string append) instead if they meant it as a single operator, or to put a space between if they really wanted to assign a stringified value as two separate operators.
From t/spec/S03-operators/brainos.t lines 15–26: (skip)
-
| #L<S03/Chaining binary precedence/"To catch">
|
|
|
| my $str = 'foo';
|
| eval '$str =~ m/bar/;';
|
| ok $! ~~ Exception, 'caught "=~" braino';
|
| ok "$!" ~~ /'~~'/, 'error for "=~" usage mentions "~~"';
|
|
|
| eval '$str !~ m/bar/;';
|
| ok $! ~~ Exception, 'caught "!~" braino';
|
| ok "$!" ~~ /'!~~'/, 'error for "!~" usage mentions "!~~"';
|
|
|
| # vim: ft=perl6
|
A negated smart match is spelled !~~.
- Container identity
VAR($a) =:= VAR($b)
See "Comparison semantics".
- Value identity
From t/spec/S03-operators/value_equivalence.t lines 15–165: (skip)
-
| # L<S03/"Chaining binary precedence" /Value identity>
|
|
|
| plan *;
|
|
|
| # === on values
|
| {
|
| ok (1 === 1), "=== on values (1)";
|
| ok (0 === 0), "=== on values (2)";
|
| ok !(0 === 1), "=== on values (3)";
|
| isa_ok (1 === 1), Bool, "=== on values (4)";
|
| ok ("abc" === "abc"), "=== on values(abc)";
|
| ok !("abc" === "ABC"), "=== on values(abc === ABC)";
|
| isa_ok ("abc" === "abc"), Bool, "=== on values (abc)";
|
| ok !(1 === 1.0), "=== on values (1 === 1.0)";
|
| ok !(1 === "1"), '=== on values (1 === "1")';
|
| ok (Mu === Mu), '=== on values (Mu === Mu)';
|
| isa_ok (Mu === Mu), Bool, '=== on values (Mu === Mu)';
|
| }
|
|
|
| # more value tests
|
| {
|
| #?rakudo todo "=== broken on Rat"
|
| ok 1/2 === 1/2, "=== on Rats";
|
| ok 1/2 !=== 3/2, "!=== on Rats";
|
| isa_ok 1/2 === 1/2, Bool, "=== on Rats yields Bool";
|
| isa_ok 1/2 !=== 3/2, Bool, "!=== on Rats yields Bool";
|
| ok 0.5e0 === 0.5e0, "=== on Nums";
|
| ok 0.5e0 !=== 1.5e0, "!=== on Nums";
|
| isa_ok 0.5e0 === 0.5e0, Bool, "=== on Nums yields Bool";
|
| isa_ok 0.5e0 !=== 1.5e0, Bool, "!=== on Nums yields Bool";
|
| }
|
|
|
| # Value types
|
| {
|
| my $a = 1;
|
| my $b = 1;
|
|
|
| ok $a === $a, "=== on value types (1-1)";
|
| ok $b === $b, "=== on value types (1-2)";
|
| ok $a === $b, "=== on value types (1-3)";
|
| isa_ok $a === $b, Bool, "=== on value types (1-4)";
|
| }
|
|
|
| {
|
| my $a = 1;
|
| my $b = 2;
|
|
|
| ok ($a === $a), "=== on value types (2-1)";
|
| ok ($b === $b), "=== on value types (2-2)";
|
| ok !($a === $b), "=== on value types (2-3)";
|
| isa_ok ($a === $a), Bool, "=== on value types (2-4)";
|
| }
|
|
|
| # Reference types
|
| {
|
| my @a = (1,2,3);
|
| my @b = (1,2,3);
|
|
|
| #?rakudo 2 todo "=== doesn't work on array references yet"
|
| ok (\@a === \@a), "=== on array references (1)";
|
| ok (\@b === \@b), "=== on array references (2)";
|
| ok !(\@a === \@b), "=== on array references (3)";
|
| isa_ok (\@a === \@a), Bool, "=== on array references (4)";
|
| }
|
|
|
| {
|
| my $a = \3;
|
| my $b = \3;
|
|
|
| ok ($a === $a), "=== on scalar references (1-1)";
|
| ok ($b === $b), "=== on scalar references (1-2)";
|
| ok !($a === $b), "=== on scalar references (1-3)";
|
| isa_ok ($a === $a), Bool, "=== on scalar references (1-4)";
|
| }
|
|
|
| {
|
| my $a = { 3 };
|
| my $b = { 3 };
|
|
|
| ok ($a === $a), "=== on sub references (1-1)";
|
| ok ($b === $b), "=== on sub references (1-2)";
|
| ok !($a === $b), "=== on sub references (1-3)";
|
| isa_ok ($a === $a), Bool, "=== on sub references (1-4)";
|
| }
|
|
|
| {
|
| ok (&say === &say), "=== on sub references (2-1)";
|
| ok (&map === &map), "=== on sub references (2-2)";
|
| ok !(&say === &map), "=== on sub references (2-3)";
|
| isa_ok (&say === &say), Bool, "=== on sub references (2-4)";
|
| }
|
|
|
| {
|
| my $num = 3;
|
| my $a = \$num;
|
| my $b = \$num;
|
|
|
| ok ($a === $a), "=== on scalar references (2-1)";
|
| ok ($b === $b), "=== on scalar references (2-2)";
|
| #?rakudo todo "=== fail"
|
| ok ($a === $b), "=== on scalar references (2-3)";
|
| isa_ok ($a === $a), Bool, "=== on scalar references (2-4)";
|
| }
|
|
|
| {
|
| ok !([1,2,3] === [4,5,6]), "=== on anonymous array references (1)";
|
| ok !([1,2,3] === [1,2,3]), "=== on anonymous array references (2)";
|
| ok !([] === []), "=== on anonymous array references (3)";
|
| isa_ok ([1,2,3] === [4,5,6]), Bool, "=== on anonymous array references (4)";
|
| }
|
|
|
| {
|
| ok !({a => 1} === {a => 2}), "=== on anonymous hash references (1)";
|
| ok !({a => 1} === {a => 1}), "=== on anonymous hash references (2)";
|
| isa_ok ({a => 1} === {a => 2}), Bool, "=== on anonymous hash references (3)";
|
| }
|
|
|
| {
|
| ok !(\3 === \4), "=== on anonymous scalar references (1)";
|
| ok !(\3 === \3), "=== on anonymous scalar references (2)";
|
| ok !(\Mu === \Mu), "=== on anonymous scalar references (3)";
|
| isa_ok (\3 === \4), Bool, "=== on anonymous scalar references (4)";
|
| }
|
|
|
| # Chained === (not specced, but obvious)
|
| {
|
| ok (3 === 3 === 3), "chained === (1)";
|
| ok !(3 === 3 === 4), "chained === (2)";
|
| }
|
|
|
| # Subparam binding doesn't affect === test
|
| {
|
| my $foo;
|
| my $test = -> $arg { $foo === $arg };
|
|
|
| $foo = 3;
|
| ok $test($foo), "subparam binding doesn't affect === (1)";
|
| ok $test(3), "subparam binding doesn't affect === (2)";
|
|
|
| ok !$test(4), "subparam binding doesn't affect === (3)";
|
| my $bar = 4;
|
| ok !$test($bar), "subparam binding doesn't affect === (4)";
|
| }
|
|
|
| {
|
| my $a = 1;
|
| my $b = 2;
|
| is($a === $a, Bool::True, '=== returns Bool::True when true');
|
| is($a === $b, Bool::False, '=== returns Bool::False when false');
|
| }
|
|
|
$x === $y
For objects that are not value types, their identities are their values. (Identity is returned by the .WHICH metamethod.) The actual contents of the objects are ignored. These semantics are those used by hashes that allow objects for keys. See also "Comparison semantics".
Note that === is defined with an (Any,Any) signature, and therefore autothreads over junctions; hence it cannot be used to determine if two objects are the same, if either or both of them are junctions. However, since .WHICH is a macro that always returns a value and never autothreads, you can easily work around this limitation by saying:
$junk1.WHICH eqv $junk2.WHICH
- Canonical equivalence
$obj1 eqv $obj2
Compares two objects for canonical equivalence. For value types compares the values. For object types, compares current contents according to some scheme of canonicalization. These semantics are those used by hashes that allow only values for keys (such as Perl 5 string-key hashes). See also "Comparison semantics".
Note that eqv autothreads over junctions, as do all other comparison operators. (Do not be confused by the fact that these return boolean values; in general, only boolean context forces junction collapse.)
- Negated relational operators
From t/spec/S03-operators/value_equivalence.t lines 166–180: (skip)
-
| # L<S03/"Chaining binary precedence" /Negated relational operators>
|
| {
|
| ok !(1 !=== 1), "!=== on values (1)";
|
| ok !(0 !=== 0), "!=== on values (2)";
|
| ok (1 !=== 0), "!=== on values (3)";
|
| isa_ok (1 !=== 1), Bool, "!=== on values (4)";
|
| ok !("abc" !=== "abc"), "!=== on values(abc)";
|
| ok ("abc" !=== "ABC"), "!=== on values(abc !=== ABC)";
|
| ok (1 !=== 1.0), "!=== on values (1 !=== 1.0)";
|
| ok (1 !=== "1"), '!=== on values (1 !=== "1")';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
$num !== 42
$str !eq "abc"
"foo" !~~ /^ <ident> $/
VAR($a) !=:= VAR($b)
$a !=== $b
$a !eqv $b
See "Negated relational operators".
infix:<||>, short-circuit inclusive-or
From t/spec/S03-operators/misc.t lines 54–68: (skip)
-
| # L<S03/Tight or precedence/short-circuit inclusive-or>
|
| is(2 || 3, 2, "|| returns first true value");
|
| ok(!(defined( 0 || Mu)), "|| returns last false value of list?");
|
|
|
| #?rakudo skip "state NYI"
|
| {
|
| (my @s)[0] //= 5;
|
| is @s[0], 5, '(my @s)[0] //= something works';
|
| (state @t)[0] //= 5;
|
| is @t[0], 5, '(state @t)[0] //= something works';
|
| }
|
|
|
| is(2 ?| 3, True, "boolean or (?|) returns True or False");
|
| is(0 ?| Any, False, "boolean or (?|) returns True or False");
|
|
|
$a || $b || $c ...
Returns the first argument that evaluates to a true value, otherwise returns the result of the last argument. It is specifically allowed to use a list or array both as a boolean and as a list value produced if the boolean is true:
@a = @b || @c; # broken in Perl 5; works in Perl 6
In list context this operator forces a false return to mean (). See or below for low-precedence version.
infix:<^^>, short-circuit exclusive-or
$a ^^ $b ^^ $c ...
Returns the true argument if there is one (and only one). Returns Bool::False if all arguments are false or if more than one argument is true. In list context forces a false return to mean (). See xor below for low-precedence version.
This operator short-circuits in the sense that it does not evaluate any arguments after a 2nd true result. Closely related is the reduce operator:
[^^] a(), b(), c() ...
but note that reduce operators are not macros but ordinary list operators, so c() is always called before the reduce is done.
infix:<//>, short-circuit default operator
$a // $b // $c ...
Returns the first argument that evaluates to a defined value, otherwise returns the result of the last argument. In list context forces a false return to mean (). See orelse below for a similar but not identical low-precedence version.
- Minimum and maximum
From t/spec/S03-operators/minmax.t lines 7–9: (skip)
-
| # L<S03/Tight or precedence/Minimum and maximum>
|
| # L<S03/Tight or precedence/"any value of any type may be compared with +Inf
|
| # or -Inf values">
|
$a min $b min $c ...
$a max $b max $c ...
These return the minimum or maximum value. See also the minmax listop.
Not all types can support the concept of infinity. Therefore any value of any type may be compared with +Inf or -Inf values, in which case the infinite value stands for "larger/smaller than any possible value of the type." That is,
From t/spec/S03-operators/minmax.t lines 8–9: (skip)
-
| # L<S03/Tight or precedence/"any value of any type may be compared with +Inf
|
| # or -Inf values">
|
"foo" min +Inf # "foo"
"foo" min -Inf # -Inf
"foo" max +Inf # +Inf
"foo" max -Inf # "foo"
All orderable object types must support +Inf and -Inf values as special forms of the undefined value. It's an error, however, to attempt to store an infinite value into a native type that cannot support it:
my int $max;
$max max= -Inf; # ERROR
- Conditional operator
say "My answer is: ", $maybe ?? "yes" !! "no";
Also known as the "ternary" or "trinary" operator, but we prefer "conditional" just to stop people from fighting over the terms. The operator syntactically separates the expression into three subexpressions. It first evaluates the left part in boolean context, then based on that selects one of the other two parts to evaluate. (It never evaluates both of them.) If the conditional is true it evaluates and returns the middle part; if false, the right part. The above is therefore equivalent to:
From t/spec/S03-operators/misc.t lines 20–25: (skip)
-
| # L<S03/Conditional operator precedence/Also known as the ternary or trinary operator>
|
| my $bar = "";
|
| ($str3 eq $str4) ?? ($bar = 1) !! ($bar = 0);
|
|
|
| ok($bar, "?? !!");
|
|
|
say "My answer is: ", do {
if $maybe {
"yes";
}
else {
"no";
}
};
It is a syntax error to use an operator in the middle part that binds looser in precedence, such as =.
my $x;
hmm() ?? $x = 1 !! $x = 2; # ERROR
hmm() ?? ($x = 1) !! ($x = 2); # works
Note that both sides have to be parenthesized. A partial fix is even wronger:
hmm() ?? ($x = 1) !! $x = 2; # parses, but WRONG
That actually parses as:
(
hmm() ?? ($x = 1) !! $x
) = 2;
and always assigns 2 to $x (because ($x = 1) is a valid lvalue).
And in any case, repeating the $x forces you to declare it earlier. The best don't-repeat-yourself solution is simply:
my $x = hmm() ?? 1 !! 2; # much better
infix:<?>
To catch likely errors by people familiar with C-derived languages (including Perl 5), a bare question mark in infix position will produce an error suggesting that the user use ?? !! instead.
- Flipflop ranges
start() ff end()
start() ^ff end()
start() ff^ end()
start() ^ff^ end()
- Flipflop ranges (sed style)
start() fff end()
start() ^fff end()
start() fff^ end()
start() ^fff^ end()
Operator adverbs are special-cased in the grammar, but give the appearance of being parsed as trailing unary operators at a pseudo-precedence level slightly tighter than item assignment. (They're not officially "postfix" operators because those require the absence of whitespace, and these allow whitespace. These adverbs insert themselves in the spot where the parser is expecting an infix operator, but the parser continues to look for an infix after parsing the adverb and applying it to the previous term.) Thus,
$a < 1 and $b == 2 :carefully
does the == carefully, while
$a < 1 && $b == 2 :carefully
does the && carefully because && is of tighter precedence than "comma". Use
$a < 1 && ($b == 2 :carefully)
to apply the adverb to the == operator instead. We say that == is the "topmost" operator in the sense that it is at the top of the parse tree that the adverb could possibly apply to. (It could not apply outside the parens.) If you are unsure what the topmost operator is, just ask yourself which operator would be applied last. For instance, in
+%hash{$key} :foo
The subscript happens first and the + operator happens last, so :foo would apply to that. Use
+(%hash{$key} :foo)
to apply :foo to the subscripting operator instead.
Adverbs will generally attach the way you want when you say things like
1 op $x+2 :mod($x)
The proposed internal testing syntax makes use of these precedence rules:
$x eqv $y+2 :ok<$x is equivalent to $y+2>;
Here the adverb is considered to be modifying the eqv operator.
From t/spec/S03-operators/binding-nested.t lines 5–342: (skip)
-
| # L<S03/Item assignment precedence>
|
|
|
| # Tests for binding multidimensional structures.
|
|
|
| plan 43;
|
|
|
| # Nested refs as RHS in a binding operation
|
| {
|
| my $struct = [
|
| "ignored",
|
| {
|
| key => {
|
| ignored => 23,
|
| subkey => [
|
| "ignored",
|
| 42,
|
| ],
|
| },
|
| ignored => 19,
|
| },
|
| ];
|
|
|
| is $struct[1]<key><subkey>[1], 42, "basic sanity (1)";
|
|
|
| my $abbrev := $struct[1]<key><subkey>[1];
|
| is $abbrev, 42,
|
| "using a multidimensional structure as RHS in a binding op works (1)";
|
|
|
| $struct[1]<key><subkey>[1] = 43;
|
| is $abbrev, 43,
|
| "using a multidimensional structure as RHS in a binding op works (2)";
|
|
|
| $abbrev = 44;
|
| is $struct[1]<key><subkey>[1], 44,
|
| "using a multidimensional structure as RHS in a binding op works (3)";
|
| }
|
|
|
| # Nested refs as LHS in a binding operation
|
| {
|
| my $struct = [
|
| "ignored",
|
| {
|
| key => {
|
| ignored => 23,
|
| subkey => [
|
| "ignored",
|
| 42,
|
| ],
|
| },
|
| ignored => 19,
|
| },
|
| ];
|
|
|
| is $struct[1]<key><subkey>[1], 42, "basic sanity (2)";
|
|
|
| my $abbrev = 30;
|
| try { $struct[1]<key><subkey>[1] := $abbrev };
|
| is $abbrev, 30,
|
| "using a multidimensional structure as LHS in a binding op works (1)";
|
|
|
| $struct[1]<key><subkey>[1] = 31;
|
| is $abbrev, 31,
|
| "using a multidimensional structure as LHS in a binding op works (2)";
|
|
|
| $abbrev = 32;
|
| is $struct[1]<key><subkey>[1], 32,
|
| "using a multidimensional structure as LHS in a binding op works (3)";
|
| }
|
|
|
| # Evil more evil structure: with an embedded "is rw" sub!
|
| # As RHS...
|
| {
|
| my $innerstruct = {
|
| ignored => 23,
|
| subkey => [
|
| "ignored",
|
| 42,
|
| ],
|
| };
|
|
|
| my sub get_innerstruct () is rw { $innerstruct }
|
|
|
| my $struct = [
|
| "ignored",
|
| {
|
| key => &get_innerstruct,
|
| ignored => 19,
|
| },
|
| ];
|
|
|
| is $struct[1]<key>()<subkey>[1], 42, "basic sanity (3)";
|
|
|
| my $abbrev := $struct[1]<key>()<subkey>[1];
|
| is $abbrev, 42,
|
| "using a multidimensional structure with an embedded sub as RHS works (1)";
|
|
|
| $struct[1]<key>()<subkey>[1] = 43;
|
| is $abbrev, 43,
|
| "using a multidimensional structure with an embedded sub as RHS works (2)";
|
|
|
| $abbrev = 44;
|
| is $struct[1]<key>()<subkey>[1], 44,
|
| "using a multidimensional structure with an embedded sub as RHS works (3)";
|
| }
|
|
|
| # ...and as LHS
|
| {
|
| my $innerstruct = {
|
| ignored => 23,
|
| subkey => [
|
| "ignored",
|
| 42,
|
| ],
|
| };
|
|
|
| my sub get_innerstruct () is rw { $innerstruct }
|
|
|
| my $struct = [
|
| "ignored",
|
| {
|
| key => &get_innerstruct,
|
| ignored => 19,
|
| },
|
| ];
|
|
|
| is $struct[1]<key>()<subkey>[1], 42, "basic sanity (4)";
|
|
|
| my $abbrev = 30;
|
| try { $struct[1]<key>()<subkey>[1] := $abbrev };
|
| is $abbrev, 30,
|
| "using a multidimensional structure with an embedded sub as LHS works (1)";
|
|
|
| $struct[1]<key>()<subkey>[1] = 31;
|
| is $abbrev, 31,
|
| "using a multidimensional structure with an embedded sub as LHS works (2)";
|
|
|
| $abbrev = 32;
|
| is $struct[1]<key>()<subkey>[1], 32,
|
| "using a multidimensional structure with an embedded sub as LHS works (3)";
|
| }
|
|
|
| # Binding should cope with a subtree being redefined.
|
| # As RHS...
|
| {
|
| my $struct = [
|
| "ignored",
|
| {
|
| key => {
|
| ignored => 23,
|
| subkey => [
|
| "ignored",
|
| 42,
|
| ],
|
| },
|
| ignored => 19,
|
| },
|
| ];
|
|
|
| is $struct[1]<key><subkey>[1], 42, "basic sanity (5)";
|
|
|
| my $abbrev := $struct[1]<key><subkey>[1];
|
| is $abbrev, 42,
|
| "RHS binding should cope with a subtree being redefined (1)";
|
|
|
| $struct[1]<key><subkey>[1] = 43;
|
| is $abbrev, 43,
|
| "RHS binding should cope with a subtree being redefined (2)";
|
|
|
| $struct[1] = "foo";
|
| is $struct[1], "foo",
|
| "RHS binding should cope with a subtree being redefined (3)";
|
| is $abbrev, 43,
|
| "RHS binding should cope with a subtree being redefined (4)";
|
|
|
| $abbrev = 44;
|
| is $abbrev, 44,
|
| "RHS binding should cope with a subtree being redefined (5)";
|
| is $struct[1], "foo",
|
| "RHS binding should cope with a subtree being redefined (6)";
|
| }
|
|
|
| # ...and as LHS
|
| {
|
| my $struct = [
|
| "ignored",
|
| {
|
| key => {
|
| ignored => 23,
|
| subkey => [
|
| "ignored",
|
| 42,
|
| ],
|
| },
|
| ignored => 19,
|
| },
|
| ];
|
|
|
| is $struct[1]<key><subkey>[1], 42, "basic sanity (6)";
|
|
|
| my $abbrev = 42;
|
| try { $struct[1]<key><subkey>[1] := $abbrev };
|
| is $abbrev, 42,
|
| "LHS binding should cope with a subtree being redefined (1)";
|
|
|
| $struct[1]<key><subkey>[1] = 43;
|
| is $abbrev, 43,
|
| "LHS binding should cope with a subtree being redefined (2)";
|
|
|
| $struct[1] = "foo";
|
| is $struct[1], "foo",
|
| "LHS binding should cope with a subtree being redefined (3)";
|
| is $abbrev, 43,
|
| "LHS binding should cope with a subtree being redefined (4)";
|
|
|
| $abbrev = 44;
|
| is $abbrev, 44,
|
| "LHS binding should cope with a subtree being redefined (5)";
|
| is $struct[1], "foo",
|
| "LHS binding should cope with a subtree being redefined (6)";
|
| }
|
|
|
| # Tests for binding an element of a structure to an element of another
|
| # structure.
|
| {
|
| my $foo = [
|
| "ignored",
|
| {
|
| key => {
|
| ignored => 1,
|
| subkey => [
|
| "ignored",
|
| 2,
|
| ],
|
| },
|
| ignored => 3,
|
| },
|
| ];
|
|
|
| my $bar = [
|
| "ignored",
|
| {
|
| key => {
|
| ignored => 4,
|
| subkey => [
|
| "ignored",
|
| 5,
|
| ],
|
| },
|
| ignored => 6,
|
| },
|
| ];
|
|
|
| try { $bar[1]<key><subkey> := $foo[1]<key> };
|
| is (try { $bar[1]<key><subkey><subkey>[1] }), 2,
|
| "binding an element of a structure to an element of another structure works (1)";
|
|
|
| try { $foo[1]<key><subkey>[1] = 7 };
|
| is (try { $bar[1]<key><subkey><subkey>[1] }), 7,
|
| "binding an element of a structure to an element of another structure works (2)";
|
|
|
| try { $bar[1]<key><subkey><subkey>[1] = 8 };
|
| is (try { $foo[1]<key><subkey>[1] }), 8,
|
| "binding an element of a structure to an element of another structure works (3)";
|
| }
|
|
|
| # Tests for binding an element of a structure to an element of *the same*
|
| # structure, effectively creating an infinite structure.
|
| {
|
| my $struct = [
|
| "ignored",
|
| {
|
| key => {
|
| foo => "bar",
|
| subkey => [
|
| "ignored",
|
| 100,
|
| ],
|
| },
|
| ignored => 200,
|
| },
|
| ];
|
|
|
| try { $struct[1]<key><subkey>[1] := $struct[1]<key> };
|
| is (try { $struct[1]<key><subkey>[1]<foo> }), "bar",
|
| "binding an element of a structure to an element of the same structure works (1)";
|
|
|
| try { $struct[1]<key><subkey>[1]<foo> = "new_value" };
|
| is $struct[1]<key><foo>, "new_value",
|
| "binding an element of a structure to an element of the same structure works (2)";
|
|
|
| $struct[1]<key><foo> = "very_new_value";
|
| is (try { $struct[1]<key><subkey>[1]<foo> }), "very_new_value",
|
| "binding an element of a structure to an element of the same structure works (3)";
|
|
|
| $struct[1]<key><subkey>[1] = 23;
|
| is $struct[1]<key>, 23,
|
| "binding an element of a structure to an element of the same structure works (4)";
|
| }
|
|
|
| # Test that rebinding to some other value really breaks up the binding.
|
| {
|
| my $struct = [
|
| "ignored",
|
| {
|
| key => {
|
| ignored => 23,
|
| subkey => [
|
| "ignored",
|
| 42,
|
| ],
|
| },
|
| ignored => 19,
|
| },
|
| ];
|
|
|
| is $struct[1]<key><subkey>[1], 42, "basic sanity (7)";
|
|
|
| my $abbrev := $struct[1]<key><subkey>[1];
|
| is $abbrev, 42,
|
| "rebinding to some other value destroys the previous binding (1)";
|
|
|
| $struct[1]<key><subkey>[1] = 43;
|
| is $abbrev, 43,
|
| "rebinding to some other value destroys the previous binding (2)";
|
|
|
| $abbrev = 44;
|
| is $struct[1]<key><subkey>[1], 44,
|
| "rebinding to some other value destroys the previous binding (3)";
|
|
|
| $abbrev := 45;
|
| is $abbrev, 45,
|
| "rebinding to some other value destroys the previous binding (4)";
|
| is $struct[1]<key><subkey>[1], 44,
|
| "rebinding to some other value destroys the previous binding (5)";
|
| }
|
|
|
|
|
| # vim: ft=perl6
|
From t/spec/S03-operators/binding-arrays.t lines 5–239: (skip)
-
| # L<S03/Item assignment precedence>
|
|
|
| plan 47;
|
|
|
| # Binding of array elements.
|
| # See thread "Binding of array elements" on p6l started by Ingo Blechschmidt:
|
| # L<"http://www.nntp.perl.org/group/perl.perl6.language/22915">
|
| {
|
| my @array = <a b c>;
|
| my $var = "d";
|
|
|
| try { @array[1] := $var };
|
| is @array[1], "d", "basic binding of an array element (1)";
|
| unless @array[1] eq "d" {
|
| skip_rest "Skipping binding of array elements tests (not yet implemented in the normal runcore)";
|
| exit;
|
| }
|
|
|
| $var = "e";
|
| is @array[1], "e", "basic binding of an array element (2)";
|
|
|
| @array[1] = "f";
|
| is $var, "f", "basic binding of an array element (3)";
|
| }
|
|
|
| {
|
| my @array = <a b c>;
|
| my $var = "d";
|
|
|
| @array[1] := $var;
|
| $var = "e";
|
| is @array[1], "e", "binding of array elements works with .delete (1)";
|
|
|
| @array.delete(1);
|
| # $var unchanged, but assigning to $var doesn't modify @array any
|
| # longer; similarily, changing @array[1] doesn't modify $var now
|
| is $var, "e", "binding of array elements works with .delete (2)";
|
| is ~@array, "a c", "binding of array elements works with .delete (3)";
|
|
|
| $var = "f";
|
| @array[1] = "g";
|
| is $var, "f", "binding of array elements works with .delete (4)";
|
| is @array[1], "g", "binding of array elements works with .delete (5)";
|
| }
|
|
|
| {
|
| my @array = <a b c>;
|
| my $var = "d";
|
|
|
| @array[1] := $var;
|
| $var = "e";
|
| is @array[1], "e", "binding of array elements works with resetting the array (1)";
|
|
|
| @array = ();
|
| # $var unchanged, but assigning to $var doesn't modify @array any
|
| # longer; similarily, changing @array[1] doesn't modify $var now
|
| is $var, "e", "binding of array elements works with resetting the array (2)";
|
| is ~@array, "", "binding of array elements works with resetting the array (3)";
|
|
|
| $var = "f";
|
| @array[1] = "g";
|
| is $var, "f", "binding of array elements works with resetting the array (4)";
|
| is @array[1], "g", "binding of array elements works with resetting the array (5)";
|
| }
|
|
|
| {
|
| my @array = <a b c>;
|
| my $var = "d";
|
|
|
| @array[1] := $var;
|
| $var = "e";
|
| is @array[1], "e", "binding of array elements works with rebinding the array (1)";
|
|
|
| my @other_array = <x y z>;
|
| @array := @other_array;
|
| # $var unchanged, but assigning to $var doesn't modify @array any
|
| # longer; similarily, changing @array[1] doesn't modify $var now
|
| is $var, "e", "binding of array elements works with rebinding the array (2)";
|
| is ~@array, "x y z", "binding of array elements works with rebinding the array (3)";
|
|
|
| $var = "f";
|
| @array[1] = "g";
|
| is $var, "f", "binding of array elements works with rebinding the array (4)";
|
| is @array[1], "g", "binding of array elements works with rebinding the array (5)";
|
| }
|
|
|
| #?rakudo skip 'lexically scoped subs'
|
| {
|
| my sub foo (@arr) { @arr[1] = "new_value" }
|
|
|
| my @array = <a b c>;
|
| my $var = "d";
|
| @array[1] := $var;
|
|
|
| foo @array;
|
| is $var, "new_value", "passing an array to a sub expecting an array behaves correctly (1)";
|
| is ~@array, "a new_value c", "passing an array to a sub expecting an array behaves correctly (2)";
|
| }
|
|
|
| #?rakudo skip 'lexically scoped subs'
|
| {
|
| my sub foo (Array $arr) { $arr[1] = "new_value" }
|
|
|
| my @array = <a b c>;
|
| my $var = "d";
|
| @array[1] := $var;
|
|
|
| foo @array;
|
| is $var, "new_value", "passing an array to a sub expecting an arrayref behaves correctly (1)";
|
| is ~@array, "a new_value c", "passing an array to a sub expecting an arrayref behaves correctly (2)";
|
| }
|
|
|
| #?rakudo skip 'lexically scoped subs'
|
| {
|
| my sub foo (@args) { @args[1] = "new_value" }
|
|
|
| my @array = <a b c>;
|
| my $var = "d";
|
| @array[1] := $var;
|
|
|
| foo @array;
|
| is $var, "new_value", "passing an array to a slurpying sub behaves correctly (1)";
|
| is ~@array, "a new_value c", "passing an array to a slurpying sub behaves correctly (2)";
|
| }
|
|
|
| #?rakudo skip 'lexically scoped subs'
|
| {
|
| my sub foo (@args) { push @args, "new_value" }
|
|
|
| my @array = <a b c>;
|
| my $var = "d";
|
| @array[1] := $var;
|
|
|
| foo @array;
|
| is $var, "d", "passing an array to a slurpying sub behaves correctly (3)";
|
| is ~@array, "a d c", "passing an array to a slurpying sub behaves correctly (4)";
|
| }
|
|
|
| # Binding of not yet existing elements should autovivify
|
| {
|
| my @array;
|
| my $var = "d";
|
|
|
| lives_ok { @array[1] := $var },
|
| "binding of not yet existing elements should autovivify (1)";
|
| is @array[1], "d", "binding of not yet existing elements should autovivify (2)";
|
|
|
| $var = "e";
|
| is @array[1], "e", "binding of not yet existing elements should autovivify (3)";
|
| is $var, "e", "binding of not yet existing elements should autovivify (4)";
|
| }
|
|
|
| # Binding with .splice
|
| #?rakudo skip 'splice'
|
| {
|
| my @array = <a b c>;
|
| my $var = "d";
|
|
|
| @array[1] := $var;
|
| $var = "e";
|
| is @array[1], "e", "binding of array elements works with splice (1)";
|
|
|
| splice @array, 1, 1, ();
|
| # $var unchanged, but assigning to $var doesn't modify @array any
|
| # longer; similarily, changing @array[1] doesn't modify $var now
|
| is $var, "e", "binding of array elements works with splice (2)";
|
| is ~@array, "a c", "binding of array elements works with splice (3)";
|
|
|
| $var = "f";
|
| @array[1] = "g";
|
| is $var, "f", "binding of array elements works with splice (4)";
|
| is @array[1], "g", "binding of array elements works with splice (5)";
|
| }
|
|
|
| # Assignment (not binding) creates new containers
|
| {
|
| my @array = <a b c>;
|
| my $var = "d";
|
|
|
| @array[1] := $var;
|
| $var = "e";
|
| is @array[1], "e", "array assignment creates new containers (1)";
|
|
|
| my @new_array = @array;
|
| $var = "f";
|
| # @array[$idx] and $var are now "f", but @new_array is unchanged.
|
| is $var, "f", "array assignment creates new containers (2)";
|
| is ~@array, "a f c", "array assignment creates new containers (3)";
|
| #?rakudo todo 'unknown'
|
| is ~@new_array, "a e c", "array assignment creates new containers (4)";
|
| }
|
|
|
| # Binding does not create new containers
|
| {
|
| my @array = <a b c>;
|
| my $var = "d";
|
|
|
| @array[1] := $var;
|
| $var = "e";
|
| is @array[1], "e", "array binding does not create new containers (1)";
|
|
|
| my @new_array := @array;
|
| $var = "f";
|
| # @array[$idx] and $var are now "f", but @new_array is unchanged.
|
| is $var, "f", "array binding does not create new containers (2)";
|
| is ~@array, "a f c", "array binding does not create new containers (3)";
|
| is ~@new_array, "a f c", "array binding does not create new containers (4)";
|
| }
|
|
|
| # Binding @array := $arrayref.
|
| # See
|
| # http://colabti.de/irclogger/irclogger_log/perl6?date=2005-11-06,Sun&sel=388#l564
|
| # and consider the magic behind parameter binding (which is really normal
|
| # binding).
|
| {
|
| my $arrayref = [<a b c>];
|
| my @array := $arrayref;
|
|
|
| is +@array, 3, 'binding @array := $arrayref works (1)';
|
|
|
| @array[1] = "B";
|
| is ~$arrayref, "a B c", 'binding @array := $arrayref works (2)';
|
| is ~@array, "a B c", 'binding @array := $arrayref works (3)';
|
| }
|
|
|
| # RT #61566
|
| {
|
| eval 'my @rt61566 := 1';
|
| ok $! ~~ Exception, "Can't bind Int to array";
|
| # TODO: check that the error is the right one
|
| # <pmichaud> you should get a "Int does not do Positional role"
|
| # exception or something like that.
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S03-operators/binding-attributes.t lines 4–82: (skip)
-
| # L<S03/Item assignment precedence>
|
|
|
| plan 12;
|
|
|
| # Tests for binding public and private instance and class attributes
|
|
|
| # Public instance attributes
|
| {
|
| my $var = 42;
|
| class Klass1 { has $.x; method bind { $.x := $var } }
|
|
|
| my $obj1 = Klass1.new;
|
| try { $obj1.bind() };
|
|
|
| #?pugs 3 todo 'bug'
|
| is $obj1.x, 42, "binding public instance attribute (1)";
|
| $var = 23;
|
| is $obj1.x, 23, "binding public instance attribute (2)";
|
| $obj1.x = 19;
|
| is $var, 19, "binding public instance attribute (3)";
|
| }
|
|
|
| # Private instance attributes
|
| {
|
| my $var = 42;
|
| class Klass2 {
|
| has $x;
|
| method bind { $x := $var }
|
| method get_x { try { $x } }
|
| method set_x ($new_x) { try { $x = $new_x } }
|
| }
|
|
|
| my $obj2 = Klass2.new;
|
| try { $obj2.bind() };
|
|
|
| #?pugs 3 todo 'bug'
|
| is $obj2.get_x, 42, "binding private instance attribute (1)";
|
| $var = 23;
|
| is $obj2.get_x, 23, "binding private instance attribute (2)";
|
| $obj2.set_x(19);
|
| is $var, 19, "binding private instance attribute (3)";
|
| }
|
|
|
| # Public class attributes
|
| {
|
| my $var = 42;
|
| class Klass3 { our $.x; method bind { $.x := $var } }
|
|
|
| try { Klass3.bind() };
|
|
|
| #?pugs 3 todo 'bug'
|
| is try { Klass3.x }, 42, "binding public class attribute (1)";
|
| $var = 23;
|
| is try { Klass3.x }, 23, "binding public class attribute (2)";
|
| try { Klass3.x = 19 };
|
| is $var, 19, "binding public class attribute (3)";
|
| }
|
|
|
| # Private class attributes
|
| {
|
| my $var = 42;
|
| class Klass4 {
|
| our $x;
|
| method bind { $x := $var }
|
| method get_x { $x }
|
| method set_x ($new_x) { $x = $new_x }
|
| }
|
|
|
| try { Klass4.bind() };
|
|
|
| is Klass4.get_x, 42, "binding private class attribute (1)";
|
| $var = 23;
|
| is Klass4.get_x, 23, "binding private class attribute (2)";
|
| Klass4.set_x(19);
|
| is $var, 19, "binding private class attribute (3)";
|
| }
|
|
|
|
|
| # vim: ft=perl6
|
From t/spec/S03-operators/binding-subs.t lines 7–115: (skip)
-
| # L<S03/Item assignment precedence>
|
|
|
| # Tests for rebinding subroutines themselves
|
|
|
| {
|
| my sub foo { 42 }
|
| my sub bar { 41 }
|
|
|
| is(foo(), 42, 'before sub redefinition');
|
|
|
| &foo := &bar;
|
| is(foo(), 41, 'after sub redefinition');
|
| }
|
|
|
| # Since regexes are methods, token redefinition should work the same way
|
|
|
| package TokenTest {
|
| token foo { <[ab]> }
|
| token bar { <[ef]> }
|
|
|
| my $target = 'cat';
|
| my Bool $bool;
|
|
|
| ok($bool = ($target ~~ m/<foo>/), 'before token redefinition');
|
|
|
| &foo := &bar;
|
| ok(not($bool = ($target ~~ m/<foo>/)), 'after token redefinition');
|
| }
|
|
|
| # Tests for binding the return value of subroutines (both as RHS and LHS).
|
|
|
| {
|
| my sub foo { 42 }
|
|
|
| my $var := foo();
|
| is $var, 42,
|
| "binding a var to the return value of a sub (a constant) works (1)";
|
|
|
| dies_ok { $var = 23 },
|
| "binding a var to the return value of a sub (a constant) works (2)";
|
| }
|
|
|
| =begin unspecced
|
|
|
| {
|
| my sub foo { 42 }
|
|
|
| dies_ok { foo() := 23 },
|
| "using the constant return value of a sub as the LHS in a binding operation dies";
|
| }
|
|
|
| There're two ways one can argue:
|
| * 42 is constant, and rebinding constants doesn't work, so foo() := 23 should
|
| die.
|
| * 42 is constant, but the implicit return() packs the constant 42 into a
|
| readonly 42, and readonly may be rebound.
|
| To clear the terminology,
|
| 42 # 42 is a constant
|
| sub foo ($a) {...} # $a is a readonly
|
|
|
| =end unspecced
|
|
|
| {
|
| my sub foo { my $var = 42; $var }
|
|
|
| my $var := foo();
|
| is $var, 42,
|
| "binding a var to the return value of a sub (a variable) works (1)";
|
|
|
| dies_ok { $var = 23 },
|
| "binding a var to the return value of a sub (a variable) works (2)";
|
| }
|
|
|
| {
|
| my sub foo is rw { my $var = 42; $var }
|
|
|
| my $var := foo();
|
| is $var, 42,
|
| "binding a var to the return value of an 'is rw' sub (a variable) works (1)";
|
|
|
| lives_ok { $var = 23 },
|
| "binding a var to the return value of an 'is rw' sub (a variable) works (2)";
|
| is $var, 23,
|
| "binding a var to the return value of an 'is rw' sub (a variable) works (3)";
|
| }
|
|
|
| {
|
| my sub foo is rw { my $var = 42; $var }
|
|
|
| #?pugs todo 'bug'
|
| lives_ok { foo() := 23 },
|
| "using the variable return value of an 'is rw' sub as the LHS in a binding operation works";
|
| }
|
|
|
| =begin discussion
|
|
|
| Should the constant return value be autopromoted to a var? Or should it stay a
|
| constant?
|
|
|
| {
|
| my sub foo is rw { 42 }
|
|
|
| dies_ok/lives_ok { foo() := 23 },
|
| "using the constant return value of an 'is rw' sub as the LHS in a binding operation behaves correctly";
|
| }
|
|
|
| =end discussion
|
|
|
| # vim: ft=perl6
|
From t/spec/S03-operators/binding-hashes.t lines 5–185: (skip)
-
| # L<S03/Item assignment precedence>
|
|
|
| plan 37;
|
|
|
| # Binding of hash elements.
|
| # See thread "Binding of array elements" on p6l started by Ingo Blechschmidt:
|
| # L<"http://www.nntp.perl.org/group/perl.perl6.language/22915">
|
| {
|
| my %hash = (:a<x>, :b<y>, :c<z>);
|
| my $var = "d";
|
|
|
| try { %hash<b> := $var };
|
| is %hash<b>, "d", "basic binding of a hash element (1)";
|
| unless %hash<b> eq "d" {
|
| skip_rest "Skipping binding of hash elements tests (not yet implemented in the normal runcore)";
|
| exit;
|
| }
|
|
|
| $var = "e";
|
| is %hash<b>, "e", "basic binding of a hash element (2)";
|
|
|
| %hash<b> = "f";
|
| is $var, "f", "basic binding of a hash element (3)";
|
| }
|
|
|
| {
|
| my %hash = (:a<x>, :b<y>, :c<z>);
|
| my $var = "d";
|
|
|
| %hash<b> := $var;
|
| $var = "e";
|
| is %hash<b>, "e", "binding of hash elements works with .delete (1)";
|
|
|
| %hash.delete("b");
|
| # $var unchanged, but assigning to $var doesn't modify @hash any
|
| # longer; similarily, changing @hash[1] doesn't modify $var now
|
| is $var, "e", "binding of hash elements works with .delete (2)";
|
| is ~%hash.values.sort, "x z", "binding of hash elements works with .delete (3)";
|
|
|
| $var = "f";
|
| %hash<b> = "g";
|
| is $var, "f", "binding of hash elements works with .delete (4)";
|
| is %hash<b>, "g", "binding of hash elements works with .delete (5)";
|
| }
|
|
|
| {
|
| my %hash = (:a<x>, :b<y>, :c<z>);
|
| my $var = "d";
|
|
|
| %hash<b> := $var;
|
| $var = "e";
|
| is %hash<b>, "e", "binding of hash elements works with resetting the hash (1)";
|
|
|
| %hash = ();
|
| # $var unchanged, but assigning to $var doesn't modify @hash any
|
| # longer; similarily, changing @hash[1] doesn't modify $var now
|
| is $var, "e", "binding of hash elements works with resetting the hash (2)";
|
| is ~%hash, "", "binding of hash elements works with resetting the hash (3)";
|
|
|
| $var = "f";
|
| %hash<b> = "g";
|
| is $var, "f", "binding of hash elements works with resetting the hash (4)";
|
| is %hash<b>, "g", "binding of hash elements works with resetting the hash (5)";
|
| }
|
|
|
| {
|
| my %hash = (:a<x>, :b<y>, :c<z>);
|
| my $var = "d";
|
|
|
| %hash<b> := $var;
|
| $var = "e";
|
| is %hash<b>, "e", "binding of hash elements works with rebinding the hash (1)";
|
|
|
| my %other_hash = (:p<q>, :r<s>, :t<u>);
|
| %hash := %other_hash;
|
| # $var unchanged, but assigning to $var doesn't modify @hash any
|
| # longer; similarily, changing @hash[1] doesn't modify $var now
|
| is $var, "e", "binding of hash elements works with rebinding the hash (2)";
|
| is ~%hash.values.sort, "q s u",
|
| "binding of hash elements works with rebinding the hash (3)";
|
|
|
| $var = "f";
|
| %hash<b> = "g";
|
| is $var, "f", "binding of hash elements works with rebinding the hash (4)";
|
| is %hash<b>, "g", "binding of hash elements works with rebinding the hash (5)";
|
| }
|
|
|
| #?rakudo skip 'lexically scoped subs'
|
| {
|
| my sub foo (%h) { %h<b> = "new_value" }
|
|
|
| my %hash = (:a<x>, :b<y>, :c<z>);
|
| my $var = "d";
|
| %hash<b> := $var;
|
|
|
| foo %hash;
|
| is $var, "new_value", "passing a hash to a sub expecting a hash behaves correctly (1)";
|
| is ~%hash.values.sort, "new_value x z",
|
| "passing a hash to a sub expecting a hash behaves correctly (2)";
|
| }
|
|
|
| #?rakudo skip 'lexically scoped subs'
|
| {
|
| my sub foo (Hash $h) { $h<b> = "new_value" }
|
|
|
| my %hash = (:a<x>, :b<y>, :c<z>);
|
| my $var = "d";
|
| %hash<b> := $var;
|
|
|
| foo %hash;
|
| is $var, "new_value",
|
| "passing a hash to a sub expecting a hashref behaves correctly (1)";
|
| is ~%hash.values.sort, "new_value x z",
|
| "passing a hash to a sub expecting a hashref behaves correctly (2)";
|
| }
|
|
|
| # Binding of not yet existing elements should autovivify
|
| {
|
| my %hash;
|
| my $var = "d";
|
|
|
| lives_ok { %hash<b> := $var },
|
| "binding of not yet existing elements should autovivify (1)";
|
| is %hash<b>, "d", "binding of not yet existing elements should autovivify (2)";
|
|
|
| $var = "e";
|
| is %hash<b>, "e", "binding of not yet existing elements should autovivify (3)";
|
| is $var, "e", "binding of not yet existing elements should autovivify (4)";
|
| }
|
|
|
| # Assignment (not binding) creates new containers
|
| {
|
| my %hash = (:a<x>, :b<y>, :c<z>);
|
| my $var = "d";
|
|
|
| %hash<b> := $var;
|
| $var = "e";
|
| is %hash<b>, "e", "hash assignment creates new containers (1)";
|
|
|
| my %new_hash = %hash;
|
| $var = "f";
|
| # %hash<b> and $var are now "f", but %new_hash is unchanged.
|
| is $var, "f", "hash assignment creates new containers (2)";
|
| is ~%hash\ .values.sort, "f x z", "hash assignment creates new containers (3)";
|
| is ~%new_hash.values.sort, "e x z", "hash assignment creates new containers (4)";
|
| }
|
|
|
| # Binding does not create new containers
|
| {
|
| my %hash = (:a<x>, :b<y>, :c<z>);
|
| my $var = "d";
|
|
|
| %hash<b> := $var;
|
| $var = "e";
|
| is %hash<b>, "e", "hash binding does not create new containers (1)";
|
|
|
| my %new_hash := %hash;
|
| $var = "f";
|
| # %hash<b> and $var are now "f", but %new_hash is unchanged.
|
| is $var, "f", "hash binding does not create new containers (2)";
|
| is ~%hash\ .values.sort, "f x z", "hash binding does not create new containers (3)";
|
| is ~%new_hash.values.sort, "f x z", "hash binding does not create new containers (4)";
|
| }
|
|
|
| # Binding %hash := $hashref.
|
| # See
|
| # http://colabti.de/irclogger/irclogger_log/perl6?date=2005-11-06,Sun&sel=388#l564
|
| # and consider the magic behind parameter binding (which is really normal
|
| # binding).
|
| {
|
| my $hashref = { a => "a", b => "b" };
|
| my %hash := $hashref;
|
|
|
| is +%hash, 2, 'binding %hash := $hashref works (1)';
|
|
|
| %hash<b> = "c";
|
| is ~$hashref.values.sort, "a c", 'binding %hash := $hashref works (2)';
|
| is ~%hash\ .values.sort, "a c", 'binding %hash := $hashref works (3)';
|
| }
|
|
|
| # vim: ft=perl6
|
infix:<=>
$x = 1, $y = 2;
With simple lvalues, = has this precedence, which is tighter than comma. (List assignments have listop precedence below.)
infix:<:=>, run-time binding
$signature := $capture
A new form of assignment is present in Perl 6, called binding, used in place of typeglob assignment. It is performed with the := operator. Instead of replacing the value in a container like normal assignment, it replaces the container itself. For instance:
From t/spec/S03-operators/binding-scalars.t lines 9–134: (skip)
-
| # L<S03/Item assignment precedence/replaces the container itself For instance>
|
|
|
| =end head1 Binding tests
|
|
|
| plan 28;
|
|
|
| # Basic scalar binding tests
|
| {
|
| my $x = 'Just Another';
|
| is($x, 'Just Another', 'normal assignment works');
|
|
|
| my $y := $x;
|
| is($y, 'Just Another', 'y is now bound to x');
|
|
|
| ok($y =:= $x, 'y is bound to x (we checked with the =:= identity op)');
|
|
|
| my $z = $x;
|
| is($z, 'Just Another', 'z is not bound to x');
|
|
|
| ok(!($z =:= $x), 'z is not bound to x (we checked with the =:= identity op)');
|
|
|
| $y = 'Perl Hacker';
|
| is($y, 'Perl Hacker', 'y has been changed to "Perl Hacker"');
|
| is($x, 'Perl Hacker', 'x has also been changed to "Perl Hacker"');
|
|
|
| is($z, 'Just Another', 'z is still "Just Another" because it was not bound to x');
|
| }
|
|
|
|
|
| # Binding and $CALLER::
|
| #?rakudo skip 'is context, traits followed by binding'
|
| #XXX This can pass bogusly (was doing for Rakudo for a while).
|
| {
|
| sub bar {
|
| return $CALLER::a eq $CALLER::b;
|
| }
|
|
|
| sub foo {
|
| my $a is context = "foo";
|
| my $b is context := $a;
|
| return bar(); # && bar2();
|
| }
|
|
|
| ok(foo(), "CALLER resolves bindings in caller's dynamic scope");
|
| }
|
|
|
| # Binding to swap
|
| #?rakudo skip 'list binding'
|
| {
|
| my $a = "a";
|
| my $b = "b";
|
|
|
| ($a, $b) := ($b, $a);
|
| is($a, 'b', '$a has been changed to "b"');
|
| is($b, 'a', '$b has been changed to "a"');
|
|
|
| $a = "c";
|
| is($a, 'c', 'binding to swap didn\'t make the vars readonly');
|
| }
|
|
|
| # More tests for binding a list
|
| #?rakudo skip 'list binding'
|
| {
|
| my $a = "a";
|
| my $b = "b";
|
| my $c = "c";
|
|
|
| ($a, $b) := ($c, $c);
|
| is($a, 'c', 'binding a list literal worked (1)');
|
| is($b, 'c', 'binding a list literal worked (2)');
|
|
|
| $c = "d";
|
| is($a, 'd', 'binding a list literal really worked (1)');
|
| is($b, 'd', 'binding a list literal really worked (2)');
|
| }
|
|
|
|
|
| # Binding subroutine parameters
|
| # XXX! When executed in interactive Pugs, the following test works!
|
| {
|
| my $a;
|
| my $b = sub ($arg) { $a := $arg };
|
| my $val = 42;
|
|
|
| $b($val);
|
| is $a, 42, "bound readonly sub param was bound correctly (1)";
|
| $val++;
|
| is $a, 43, "bound readonly sub param was bound correctly (2)";
|
|
|
| dies_ok { $a = 23 },
|
| "bound readonly sub param remains readonly (1)";
|
| is $a, 43,
|
| "bound readonly sub param remains readonly (2)";
|
| is $val, 43,
|
| "bound readonly sub param remains readonly (3)";
|
| }
|
|
|
| {
|
| my $a;
|
| my $b = sub ($arg is rw) { $a := $arg };
|
| my $val = 42;
|
|
|
| $b($val);
|
| is $a, 42, "bound rw sub param was bound correctly (1)";
|
| $val++;
|
| is $a, 43, "bound rw sub param was bound correctly (2)";
|
|
|
| lives_ok { $a = 23 }, "bound rw sub param remains rw (1)";
|
| is $a, 23, "bound rw sub param remains rw (2)";
|
| is $val, 23, "bound rw sub param remains rw (3)";
|
| }
|
|
|
| # := actually takes subroutine parameter list
|
| #?rakudo todo 'List binding'
|
| {
|
| my $a;
|
| eval '(:$a) := (:a<foo>)';
|
| #?pugs todo
|
| is($a, "foo", "bound keyword");
|
| my @tail;
|
| eval '($a, *@tail) := (1, 2, 3)';
|
| #?pugs todo
|
| ok($a == 1 && ~@tail eq '2 3', 'bound slurpy');
|
| }
|
|
|
| # vim: ft=perl6
|
my $x = 'Just Another';
my $y := $x;
$y = 'Perl Hacker';
After this, both $x and $y contain the string "Perl Hacker", since they are really just two different names for the same variable.
There is also an identity test, =:=, which tests whether two names are bound to the same underlying variable. $x =:= $y would return true in the above example.
From t/spec/S03-operators/identity.t lines 22–161: (skip)
-
| #L<S03/Item assignment precedence/"There is also an identity test">
|
|
|
| {
|
| my $foo = 1;
|
| my $bar = 1;
|
| ok ($foo =:= $foo), '$foo =:= $foo is true';
|
| ok ($bar =:= $bar), '$bar =:= $bar is true';
|
| ok !($foo =:= $bar), '$foo =:= $bar is false';
|
| }
|
|
|
| {
|
| my $f = sub {};
|
| ok $f =:= $f, '$subref =:= $subref is true';
|
| ok &say =:= &say, '&sub =:= &sub is true';
|
| ok !($f =:= &say), '$subref1 =:= $subref2 is false';
|
| }
|
|
|
| #?rakudo skip 'binding'
|
| {
|
| my ($a, $b, $c, $d);
|
|
|
| ok !($a =:= $b), "basic sanity";
|
|
|
| $b := $a;
|
| ok ($a =:= $b), "=:= is true after rebinding (1-1)";
|
| ok ($a =:= $a), "=:= is true after rebinding (1-2)";
|
| ok ($b =:= $b), "=:= is true after rebinding (1-3)";
|
|
|
| $c := $b;
|
| ok ($c =:= $a), "=:= is true after rebinding (2-1)";
|
| ok ($c =:= $b), "=:= is true after rebinding (2-2)";
|
| ok ($c =:= $c), "=:= is true after rebinding (2-3)";
|
|
|
| $c := $d;
|
| ok !($c =:= $a), "=:= is true after rebinding (3-1)";
|
| ok !($c =:= $b), "=:= is true after rebinding (3-2)";
|
| ok ($c =:= $c), "=:= is true after rebinding (3-3)";
|
| ok ($a =:= $b), "=:= is true after rebinding (3-4)";
|
| ok ($a =:= $a), "=:= is true after rebinding (3-5)";
|
| ok ($b =:= $b), "=:= is true after rebinding (3-6)";
|
| }
|
|
|
| # Rebinding of array elements - unspecced!
|
| #?rakudo skip 'unspecced'
|
| {
|
| my @a = (1,2,3);
|
| my @b = (1,2,3);
|
|
|
| ok !(@b[1] =:= @a[1]), "rebinding of array elements (1)";
|
|
|
| try { @b[1] := @a[1] };
|
| ok (@b[1] =:= @a[1]), "rebinding of array elements (2)";
|
|
|
| @b = (1,2,3);
|
| ok !(@b[1] =:= @a[1]), "assignment destroyed the bindings (1)";
|
| @a[1] = 100;
|
| is @a[1], 100, "assignment destroyed the bindings (2)";
|
| is @b[1], 2, "assignment destroyed the bindings (3)";
|
| }
|
|
|
| # Subparam binding
|
| {
|
| my ($foo, $bar);
|
| my $test = -> $arg is rw { $foo =:= $arg };
|
|
|
| ok $test($foo), "binding of scalar subparam retains =:= (1)";
|
| ok !$test($bar), "binding of scalar subparam retains =:= (2)";
|
| #?rakudo emit #
|
| $bar := $foo;
|
| #?rakudo skip 'binding'
|
| ok $test($bar), "binding of scalar subparam retains =:= (3)";
|
| }
|
|
|
| {
|
| my ($foo, $bar);
|
| my $test = -> $arg is rw { $foo =:= $arg };
|
|
|
| ok $test($foo), "binding of scalar subparam marked is rw retains =:= (1)";
|
| ok !$test($bar), "binding of scalar subparam marked is rw retains =:= (2)";
|
| #?rakudo emit #
|
| $bar := $foo;
|
| #?rakudo skip 'binding'
|
| ok $test($bar), "binding of scalar subparam marked is rw retains =:= (3)";
|
| }
|
|
|
| # Again, unspecced that @args[0] can participate in =:=
|
| {
|
| my ($foo, $bar);
|
| my $test = -> *@args { $foo =:= @args[0] };
|
|
|
| #?pugs todo 'unspecced'
|
| #?rakudo todo 'unspecced'
|
| ok $test($foo), "binding of slurpy array subparam retains =:= (1)";
|
| ok !$test($bar), "binding of slurpy array subparam retains =:= (2)";
|
| #?rakudo emit #
|
| $bar := $foo;
|
| #?pugs todo 'unspecced'
|
| #?rakudo skip 'unspecced'
|
| ok $test($bar), "binding of slurpy array subparam retains =:= (3)";
|
| }
|
|
|
| # Again, unspecced that @args[0] can participate in =:=
|
| #?rakudo skip 'binding'
|
| {
|
| my ($foo, $bar);
|
| my $test = sub { $foo =:= @_[0] };
|
|
|
| #?pugs todo 'unspecced'
|
| #?rakudo todo 'unspecced'
|
| ok $test($foo), "binding of implicit @_ subparam retains =:= (1)";
|
| ok !$test($bar), "binding of implicit @_ subparam retains =:= (2)";
|
| $bar := $foo;
|
| #?pugs todo 'unspecced'
|
| #?rakudo todo 'unspecced'
|
| ok $test($bar), "binding of implicit @_ subparam retains =:= (3)";
|
| }
|
|
|
| class TestObj { has $!a }
|
|
|
| #?rakudo skip 'binding'
|
| {
|
| my $foo = ::TestObj.new(:a<3>);
|
| my $bar = ::TestObj.new(:a<3>);
|
| my $baz = $foo;
|
| my $frop := $foo;
|
|
|
| ok(!($foo =:= $bar), "two identical objects are not the same object");
|
| ok(!($foo =:= $baz), "two references to one object are still not the same object");
|
| ok(($foo =:= $frop), "binding makes two objects the same object");
|
| }
|
|
|
| #?rakudo todo 'misuse of =:='
|
| {
|
| ok (Mu =:= Mu) ~~ Failure, 'misuse of =:= is failure (Mu)';
|
| ok (1 =:= '1') ~~ Failure, 'misuse of =:= is failure (literals)';
|
| ok (1 =:= 2) ~~ Failure, 'misuse of =:= is failure (!=)';
|
| ok (1 =:= 1) ~~ Failure, 'misuse of =:= is failure (even when ==)';
|
| }
|
|
|
| # vim: ft=perl6
|
The binding fails if the type of the variable being bound is sufficiently inconsistent with the type of the current declaration. Strictly speaking, any variation on
my Any $x;
$x := [1,2,3];
should fail because the type being bound is not consistent with Scalar of Any, but since the Any type is not a real instantiable type but a generic (non)constraint, and Scalar of Any is sort of a double non-constraint similar to Any, we treat this situation specially as the equivalent of binding to a typeless variable.
infix:<::=>, bind and make readonly
From t/spec/S03-operators/binding-ro.t lines 6–36: (skip)
-
| # L<S03/Item assignment precedence/bind and make readonly>
|
|
|
| {
|
| my $x = 5;
|
| my $y = 3;
|
| $x ::= $y;
|
| is $x, 3, '::= on scalars took the value from the RHS';
|
| dies_ok { $x = 5 }; '... and made the LHS RO';
|
| is $x, 3, 'variable is still 3';
|
| }
|
|
|
| {
|
| my Int $a = 4;
|
| my Str $b;
|
| dies_ok { $b ::= $a },
|
| 'Cannot ro-bind variables with incompatible type constraints';
|
| }
|
|
|
| {
|
| my @x = <a b c>;
|
| my @y = <d e>;
|
|
|
| @x ::= @y;
|
| is @x.join('|'), 'd|e', '::= on arrays';
|
| dies_ok { @x := <3 4 foo> }, '... make RO';
|
| is @x.join('|'), 'd|e', 'value unchanged';
|
| lives_ok { @x[2] = 'k' }, 'can still assign to items of RO array';
|
| is @x.join(''), 'd|e|k', 'assignment relly worked';
|
| }
|
|
|
| # vim: ft=perl6
|
$signature ::= $capture
This does the same as :=, then marks any destination parameters as readonly (unless the individual parameter overrides this with either the rw trait or the copy trait). It's particularly useful for establishing readonly dynamic variables for a dynamic scope:
{
my $*OUT ::= open($file, :w) || die $!;
doit(); # runs with redirected stdout
}
doit(); # runs with original stdout
If doit wants to change $*OUT, it must declare its own dynamic variable. It may not simply assign to $*OUT.
Note that the semantics of ::= are virtually identical to the normal binding of arguments to formal subroutine parameters (which also default to readonly).
infix:['=>'], Pair constructor
foo => 1, bar => "baz"
Binary => is no longer just a "fancy comma". It now constructs a Pair object that can, among other things, be used to pass named arguments to functions. It provides item context to both sides. It does not actually do an assignment except in a notional sense; however its precedence is now equivalent to assignment, and it is also right associative. Note that, unlike in Perl 5, => binds tighter than comma.
- Assignment operators
+= -= **= xx= .= etc.
See "Assignment operators".
From t/spec/S03-operators/so.t lines 5–24: (skip)
-
| # L<S03/Loose unary precedence>
|
|
|
| ok(so 1, "so 1 is true");
|
| ok(so -1, "so -1 is true");
|
| ok(not so 0, "not so 0 is true");
|
| ok(so sub{}, 'so sub{} is true');
|
| ok(so "x", 'so "x" is true');
|
|
|
| my $a = 1; ok(so $a, 'so $true_var is true');
|
| my $b = 0; ok(!(so $b), 'so $false_var is not true');
|
|
|
| ok( so(so 42), "so(so 42) is true");
|
| ok(not so(so 0), "so(so 0) is false");
|
|
|
| ok(so Bool::True, "'Bool::True' is true");
|
| ok(so True, "'True' is true");
|
|
|
| is (so($b) + 1), ((so $b) + 1), 'so($b) is (so $b)';
|
|
|
| # vim: ft=perl6
|
From t/spec/S03-operators/not.t lines 5–38: (skip)
-
| # L<S03/Loose unary precedence>
|
|
|
| nok(not 1, "not 1 is false");
|
| nok(not -1, "not -1 is false");
|
| nok(!(not 0), "!not 0 is false");
|
| nok(not sub{}, 'not sub{} is false');
|
| nok(not "x", 'not "x" is false');
|
|
|
| my $a = 1; nok(not $a, 'not $not_var is false');
|
| my $b = 0; nok(!(not $b), 'not $false_var is not false');
|
|
|
| #?rakudo todo 'RT 65556'
|
| is (not($b) + 1), ((not $b) + 1), 'not($b) is (not $b)';
|
|
|
| ok( not(not 42), "not(not 42) is true");
|
| ok(!not(not 0), "not(not 0) is false");
|
|
|
| is(not Bool::True, Bool::False, "'Bool::True' is not 'Bool::False'");
|
| isa_ok(not Bool::True, Bool, "'not Bool::True' is a Bool");
|
| is(not Bool::True, False, "'Bool::True' is not 'False'");
|
| is(not True, False, "'True' is not 'False'");
|
| isa_ok(not True, Bool, "'not True' is a Bool");
|
| is(not True, Bool::False, "'True' is not 'Bool::False'");
|
|
|
| is(not Bool::False, Bool::True, "'Bool::False' is not 'Bool::True'");
|
| isa_ok(not Bool::False, Bool, "'not Bool::False' is a Bool");
|
| is(not Bool::False, True, "'Bool::False' is not 'True'");
|
| is(not False, True, "'False' is not 'True'");
|
| isa_ok(not False, Bool, "'not False' is a Bool");
|
| is(not False, Bool::True, "'False' is not 'Bool::True'");
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
prefix:<not>
not any(@args) eq '-v' | '-V'
Returns a Bool value representing the logical negation of an expression.
prefix:<so>
so any(@args) eq '-v' | '-V'
Returns a Bool value representing the logical non-negation of an expression. Mostly useful as documentation in parallel to a not when else isn't appropriate:
if not $x { print "LOL"; }
mumble();
if so $x { print "SRSLY!" }
infix:<,>, the argument separator
1, 2, 3, @many
Unlike in Perl 5, comma operator never returns the last value. (In item context it returns a list instead.)
infix:<:>, the invocant marker
say $*OUT: "howdy, world"
say($*OUT: "howdy, world")
push @array: 1,2,3
push(@array: 1,2,3)
\($object: 1,2,3, :foo, :!bar)
The colon operator parses just like a comma, but marks the argument to its left as an invocant, which has the effect of turning what would otherwise be a function call into a method call. It may only be used on the first argument of an argument list or capture, and will fail to parse if used in any other position. When used within a capture, it is not yet known what signature the capture will be bound to; if bound to a non-method's signature, the invocant merely turns into the first positional argument, as if the colon had been a comma.
To avoid confusion with other colon forms, the colon infix operator must be followed by whitespace or a terminator. It may optionally have whitespace in front of it.
Note: distinguish this infix operator from the colon in
@array.push: 1,2,3
@array.push(1,2,3): 4,5,6
push(@array, 1,2,3): 4,5,6
which is a special form that turns an ordinary function or method call into a list operator. The special form is recognized only after a dotty method call, or after the right parenthesis of a method or function call. The special form does not allow intervening whitespace, but requires whitespace before the next argument. In all other cases a colon will be parsed as the start of an adverb if possible, or otherwise the invocant marker (the infix described above).
Another way to think of it is that the special colon is allowed to add listop arguments to a parenthesized argument list only after the right parenthesis of that argument list, with the proviso that you're allowed to shorten .foo(): 1,2,3 down to .foo: 1,2,3. (But only for method calls, since ordinary functions don't need the colon in the first place to turn into a listop, just whitespace. If you try to extend a function name with a colon, it's likely to be taken as a label.)
foo $obj.bar: 1,2,3 # special, means foo($obj.bar(1,2,3))
foo $obj.bar(): 1,2,3 # special, means foo($obj.bar(1,2,3))
foo $obj.bar(1): 2,3 # special, means foo($obj.bar(1,2,3))
foo $obj.bar(1,2): 3 # special, means foo($obj.bar(1,2,3))
foo($obj.bar): 1,2,3 # special, means foo($obj.bar, 1,2,3)
foo($obj.bar, 1): 2,3 # special, means foo($obj.bar, 1,2,3)
foo($obj.bar, 1,2): 3 # special, means foo($obj.bar, 1,2,3)
foo $obj.bar : 1,2,3 # infix:<:>, means $obj.bar.foo(1,2,3)
foo ($obj.bar): 1,2,3 # infix:<:>, means $obj.bar.foo(1,2,3)
foo $obj.bar:1,2,3 # syntax error
foo $obj.bar :1,2,3 # syntax error
foo $obj.bar :baz # adverb, means foo($obj.bar(:baz))
foo ($obj.bar) :baz # adverb, means foo($obj.bar, :baz)
foo $obj.bar:baz # extended identifier, foo( $obj.'bar:baz' )
foo $obj.infix:<+> # extended identifier, foo( $obj.'infix:<+>' )
foo: 1,2,3 # label at statement start, else infix
The moral of the story is, if you don't know how the colon is going to bind, use whitespace or parentheses to make it clear.
List infixes all have list associativity, which means that identical infix operators work together in parallel rather than one after the other. Non-identical operators are considered non-associative and must be parenthesized for clarity.
infix:<Z>, the zip operator
From t/spec/S03-operators/misc.t lines 106–111: (skip)
-
| # L<S03/List infix precedence/"the zip operator">
|
| #for RT #73836
|
| my @z=2,3;
|
| is (2 Z 3), @z, 'joining of single items';
|
|
|
| # vim: ft=perl6
|
1,2 Z 3,4 # (1,3),(2,4)
The Z operator is actually a degenerate case of the Z zipwith metaoperator (see "Zip operators" below).
infix:<minmax>, the minmax operator
@a minmax @b
Returns a Range from the minimum element of @a and @b to the maximum element. Range elements in the input9 are treated as if their minimum and maximum values were passed individually, except that if the corresponding excludes flag is set in Range, the excludes flag is also set in the returned Range.
infix:<X>, the cross operator
From t/spec/S03-metaops/cross.t lines 6–19: (skip)
-
| # L<S03/List infix precedence/the cross operator>
|
| ok eval('<a b> X <c d>'), 'cross non-meta operator parses';
|
|
|
| {
|
| my @result = <a b> X <1 2>;
|
| is @result, <a 1 a 2 b 1 b 2>,
|
| 'non-meta cross produces expected result';
|
|
|
| }
|
|
|
| is (1, 2, 3 X** 2, 4), (1, 1, 4, 16, 9, 81), 'X** works';
|
|
|
| is ([+] 1, 2, 3 X** 2, 4), (1+1 + 4+16 + 9+81), '[+] and X** work';
|
|
|
1,2 X 3,4 # (1,3), (1,4), (2,3), (2,4)
In contrast to the zip operator, the X operator returns all possible lists formed by taking one element from each of its list arguments. The returned lists are ordered such that the rightmost elements vary most rapidly. If there are just two lists, for instance, it forms all pairs where one element is from the first list and the other one from the second, with the second element varying most rapidly. Hence you may say:
<a b> X <1 2>
and you end up with
('a', '1'), ('a', '2'), ('b', '1'), ('b', '2')
This becomes a flat list in flat context and a list of arrays in slice context:
From t/spec/S03-metaops/cross.t lines 20–29: (skip)
-
| # L<S03/List infix precedence/This becomes a flat list in>
|
| {
|
| my @result = gather {
|
| for @(1..3 X 'a'..'b') -> $n, $a {
|
| take "$n|$a"
|
| }
|
| }
|
| is @result, <1|a 1|b 2|a 2|b 3|a 3|b>, 'smooth cross operator works';
|
| }
|
|
|
From t/spec/S03-metaops/cross.t lines 30–38: (skip)
-
| # L<S03/List infix precedence/and a list of arrays in>
|
| #?rakudo skip ".slice for iterators NYI"
|
| {
|
| my @result = gather for (1..3 X 'A'..'B').slice -> $na {
|
| take $na.join(':');
|
| }
|
| is @result, <1:A 1:B 2:A 2:B 3:A 3:B>, 'chunky cross operator works';
|
| }
|
|
|
say flat(<a b> X <1 2>)
'a', '1', 'a', '2', 'b', '1', 'b', '2'
say slice(<a b> X <1 2>)
['a', '1'], ['a', '2'], ['b', '1'], ['b', '2']
The operator is list associative, so
1,2 X 3,4 X 5,6
produces
(1,3,5),(1,3,6),(1,4,5),(1,4,6),(2,3,5),(2,3,6),(2,4,5),(2,4,6)
On the other hand, if any of the lists is empty, you will end up with a null list.
Only the leftmost list may usefully be an infinite list. For instance
<a b> X 0..*
would produce
('a',0), ('a',1), ('a',2), ('a',3), ('a',4), ('a',5), ...
and you'd never get to 'b'.
- Cross hyperoperators
@files X~ '.' X~ @extensions
1..10 X* 1..10
@x Xeqv @y
etc.
See "Cross operators".
infix:<...>, the series operator.
From t/spec/S03-operators/series.t lines 4–98: (skip)
-
| # L<S03/List infix precedence/"the series operator">
|
|
|
| plan *;
|
|
|
| # single-term series
|
|
|
| is ~( 1 ... 1 ), '1', '1 ... 1';
|
| is ~( 'a' ... 'a' ), 'a', "'a' ... 'a'";
|
|
|
| # finite series that exactly hit their limit
|
|
|
| is (1 ... 5).join(', '), '1, 2, 3, 4, 5', 'simple series with one item on the LHS';
|
| is (1 ... -3).join(', '), '1, 0, -1, -2, -3', 'simple decreasing series with one item on the LHS';
|
| is (1, 3 ... 9).join(', '), '1, 3, 5, 7, 9', 'simple additive series with two items on the LHS';
|
| is (1, 0 ... -3).join(', '), '1, 0, -1, -2, -3', 'simple decreasing additive series with two items on the LHS';
|
| is (1, 3, 5 ... 9).join(', '), '1, 3, 5, 7, 9', 'simple additive series with three items on the LHS';
|
| is (1, 3, 9 ... 81).join(', '), '1, 3, 9, 27, 81', 'simple multiplicative series with three items on the LHS';
|
| is (81, 27, 9 ... 1).join(', '), '81, 27, 9, 3, 1', 'decreasing multiplicative series with three items on the LHS';
|
| is (1, { $_ + 2 } ... 9).join(', '), '1, 3, 5, 7, 9', 'simple series with one item and block closure on the LHS';
|
| is (1, *+2 ... 9).join(', '), '1, 3, 5, 7, 9', 'simple series with one item and * closure on the LHS';
|
| is (1, { $_ - 2 } ... -7).join(', '), '1, -1, -3, -5, -7', 'simple series with one item and closure on the LHS';
|
| is (1, 3, 5, { $_ + 2 } ... 13).join(', '), '1, 3, 5, 7, 9, 11, 13', 'simple series with three items and block closure on the LHS';
|
|
|
| is (1, { 1 / ((1 / $_) + 1) } ... 1/5).map({.perl}).join(', '), '1, 1/2, 1/3, 1/4, 1/5', 'tricky series with one item and closure on the LHS';
|
| is (1, { -$_ } ... 1).join(', '), '1', 'simple alternating series with one item and closure on the LHS';
|
| is (1, { -$_ } ... 3).[^5].join(', '), '1, -1, 1, -1, 1', 'simple alternating series with one item and closure on the LHS';
|
|
|
| is (1 ... 5, 6, 7).join(', '), '1, 2, 3, 4, 5, 6, 7', 'simple series with two further terms on the RHS';
|
| is (1 ... 5, 4, 3).join(', '), '1, 2, 3, 4, 5, 4, 3', 'simple series with two extra terms on the RHS';
|
| is (1 ... 5, 'xyzzy', 'plugh').join(', '), '1, 2, 3, 4, 5, xyzzy, plugh', 'simple series with two weird items on the RHS';
|
|
|
| # finite series that go past their limit
|
|
|
| is (1 ... 5.5).join(', '), '1, 2, 3, 4, 5', 'simple series with one item on the LHS';
|
| is (1 ... -3.5).join(', '), '1, 0, -1, -2, -3', 'simple decreasing series with one item on the LHS';
|
| is (1, 3 ... 10).join(', '), '1, 3, 5, 7, 9', 'simple additive series with two items on the LHS';
|
| is (1, 0 ... -3.5).join(', '), '1, 0, -1, -2, -3', 'simple decreasing additive series with two items on the LHS';
|
| is (1, 3, 5 ... 10).join(', '), '1, 3, 5, 7, 9', 'simple additive series with three items on the LHS';
|
| is (1, 3, 9 ... 100).join(', '), '1, 3, 9, 27, 81', 'simple multiplicative series with three items on the LHS';
|
| is (81, 27, 9 ... 8/9).join(', '), '81, 27, 9, 3, 1', 'decreasing multiplicative series with three items on the LHS';
|
| is (1, { $_ + 2 } ... 10).join(', '), '1, 3, 5, 7, 9', 'simple series with one item and block closure on the LHS';
|
| is (1, *+2 ... 10).join(', '), '1, 3, 5, 7, 9', 'simple series with one item and * closure on the LHS';
|
| is (1, { $_ - 2 } ... -8).join(', '), '1, -1, -3, -5, -7', 'simple series with one item and closure on the LHS';
|
| is (1, 3, 5, { $_ + 2 } ... 14).join(', '), '1, 3, 5, 7, 9, 11, 13', 'simple series with three items and block closure on the LHS';
|
|
|
| is (1, { 1 / ((1 / $_) + 1) } ... 11/60).map({.perl}).join(', '), '1, 1/2, 1/3, 1/4, 1/5', 'tricky series with one item and closure on the LHS';
|
| is (1, { -$_ } ... 0).join(', '), '1', 'simple alternating series with one item and closure on the LHS';
|
|
|
| is (1 ... 5.5, 6, 7).join(', '), '1, 2, 3, 4, 5, 6, 7', 'simple series with two further terms on the RHS';
|
| is (1 ... 5.5, 4, 3).join(', '), '1, 2, 3, 4, 5, 4, 3', 'simple series with two extra terms on the RHS';
|
| is (1 ... 5.5, 'xyzzy', 'plugh').join(', '), '1, 2, 3, 4, 5, xyzzy, plugh', 'simple series with two weird items on the RHS';
|
|
|
| # infinite series without limits
|
|
|
| is (1 ... *).[^5].join(', '), '1, 2, 3, 4, 5', 'simple series with one item on the LHS';
|
| is (1, 3 ... *).[^5].join(', '), '1, 3, 5, 7, 9', 'simple additive series with two items on the LHS';
|
| is (1, 0 ... *).[^5].join(', '), '1, 0, -1, -2, -3', 'simple decreasing additive series with two items on the LHS';
|
| is (1, 3, 5 ... *).[^5].join(', '), '1, 3, 5, 7, 9', 'simple additive series with three items on the LHS';
|
| is (8, 7, 6 ... *).[^5].join(', '), '8, 7, 6, 5, 4', 'simple decreasing additive series with three items on the LHS';
|
| is (1, 3, 9 ... *).[^5].join(', '), '1, 3, 9, 27, 81', 'simple multiplicative series with three items on the LHS';
|
| is (81, 27, 9 ... *).[^5].join(', '), '81, 27, 9, 3, 1', 'decreasing multiplicative series with three items on the LHS';
|
| is (1, { $_ + 2 } ... *).[^5].join(', '), '1, 3, 5, 7, 9', 'simple series with one item and block closure on the LHS';
|
| is (1, *+2 ... *).[^5].join(', '), '1, 3, 5, 7, 9', 'simple series with one item and * closure on the LHS';
|
| is (1, { $_ - 2 } ... *).[^5].join(', '), '1, -1, -3, -5, -7', 'simple series with one item and closure on the LHS';
|
| is (1, 3, 5, { $_ + 2 } ... *).[^7].join(', '), '1, 3, 5, 7, 9, 11, 13', 'simple series with three items and block closure on the LHS';
|
|
|
| is (1, { 1 / ((1 / $_) + 1) } ... *).[^5].map({.perl}).join(', '), '1, 1/2, 1/3, 1/4, 1/5', 'tricky series with one item and closure on the LHS';
|
| is (1, { -$_ } ... *).[^5].join(', '), '1, -1, 1, -1, 1', 'simple alternating series with one item and closure on the LHS';
|
|
|
| is (1 ... *, 6, 7).[^7].join(', '), '1, 2, 3, 4, 5, 6, 7', 'simple series with two further terms on the RHS';
|
| is (1 ... *, 4, 3).[^7].join(', '), '1, 2, 3, 4, 5, 6, 7', 'simple series with two extra terms on the RHS';
|
| is (1 ... *, 'xyzzy', 'plugh').[^7].join(', '), '1, 2, 3, 4, 5, 6, 7', 'simple series with two weird items on the RHS';
|
|
|
| # constant series
|
|
|
| is ('c', { $_ } ... *).[^10].join(', '), 'c, c, c, c, c, c, c, c, c, c', 'constant series started with letter and identity closure';
|
| is ('c', 'c' ... *).[^10].join(', '), 'c, c, c, c, c, c, c, c, c, c', 'constant series started with two letters';
|
| is ('c', 'c', 'c' ... *).[^10].join(', '), 'c, c, c, c, c, c, c, c, c, c', 'constant series started with three letters';
|
| is (1, 1 ... *).[^10].join(', '), '1, 1, 1, 1, 1, 1, 1, 1, 1, 1', 'constant series started with two numbers';
|
| is (1, 1, 1 ... *).[^10].join(', '), '1, 1, 1, 1, 1, 1, 1, 1, 1, 1', 'constant series started with three numbers';
|
|
|
| # misleading starts
|
|
|
| is (1, 1, 1, 2, 3 ... 10).[^10].join(', '), '1, 1, 1, 2, 3, 4, 5, 6, 7, 8', 'series started with three identical numbers, but then goes arithmetic';
|
| is (1, 1, 1, 2, 4 ... 16).join(', '), '1, 1, 1, 2, 4, 8, 16', 'series started with three identical numbers, but then goes geometric';
|
| is (4, 2, 1, 2, 4 ... 16).join(', '), '4, 2, 1, 2, 4, 8, 16', 'geometric series started in one direction and continues in the other';
|
|
|
| # some tests taken from Spec
|
|
|
| #?rakudo 2 skip '&prefix:<!> does not work with series yet'
|
| is (False, &prefix:<!> ... *).[^10].join(', '), "0, 1, 0, 1, 0, 1, 0, 1, 0, 1", "alternating False and True";
|
| is (False, &prefix:<!> ... *).[^10].grep(Bool).elems, 10, "alternating False and True is always Bool";
|
| is (False, { !$_ } ... *).[^10].join(', '), "0, 1, 0, 1, 0, 1, 0, 1, 0, 1", "alternating False and True";
|
| is (False, { !$_ } ... *).[^10].grep(Bool).elems, 10, "alternating False and True is always Bool";
|
|
|
From t/spec/S03-operators/series-arity2ormore.t lines 4–50: (skip)
-
| # L<S03/List infix precedence/"the series operator">
|
|
|
| plan *;
|
|
|
| # some tests without regard to ending
|
|
|
| is (1, 1, { $^a + $^b } ... *).[^6].join(', '), '1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci';
|
| #?rakudo 2 skip "get_attr_str() not implemented in class 'Perl6MultiSub' with multi operators"
|
| is (1, 1, &infix:<+> ... *).[^6].join(', '), '1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci, using "&infix:<+>"';
|
| is (1, 1, &[+] ... *).[^6].join(', '), '1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci, using "&[+]"';
|
| is (0, 1, { $^a + $^b } ... *).[^7].join(', '), '0, 1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci, 0 1 seeds';
|
| is (1, 1, 2, -> $a, $b { $a + $b } ... *).[^6].join(', '), '1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci, 3 seeds';
|
| is (1, 1, 2, 3, { $^a + $^b } ... *).[^6].join(', '), '1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci, 4 seeds';
|
| is (0, 1, 1, 2, 3, { $^a + $^b } ... *).[^7].join(', '), '0, 1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci, 5 seeds';
|
|
|
| # some tests which exactly hit a limit
|
|
|
| is (1, 1, { $^a + $^b } ... 8).join(', '), '1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci';
|
| is (1, 1, 2, -> $a, $b { $a + $b } ... 8).join(', '), '1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci, 3 seeds';
|
| is (1, 1, 2, 3, { $^a + $^b } ... 8).join(', '), '1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci, 4 seeds';
|
| # adapted from http://www.perlmonks.org/?node_id=772778
|
| #?rakudo 2 skip "get_attr_str() not implemented in class 'Perl6MultiSub' with multi operators"
|
| is (42, 24, &[%] ... 0)[*-2], 6, 'arity-2 GCD';
|
| is (42, 24, &[%] ...^ 0)[*-1], 6, 'arity-2 GCD with excluded limit';
|
| is (42, 24, * % * ... 0)[*-2], 6, 'arity-2 GCD';
|
| #?rakudo skip "...^ NYI"
|
| is (42, 24, * % * ...^ 0)[*-1], 6, 'arity-2 GCD with excluded limit';
|
|
|
| # some tests which go past a limit
|
|
|
| is (1, 1, { $^a + $^b } ... 9).join(', '), '1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci';
|
| is (1, 1, 2, -> $a, $b { $a + $b } ... 9).join(', '), '1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci, 3 seeds';
|
| is (1, 1, 2, 3, { $^a + $^b } ... 9).join(', '), '1, 1, 2, 3, 5, 8', 'arity-2 Fibonacci, 4 seeds';
|
|
|
| # series with slurpy functions
|
|
|
| {
|
| sub nextprime( *@prev_primes ) {
|
| my $current = @prev_primes[*-1];
|
| 1 while ++$current % any(@prev_primes) == 0;
|
| return $current;
|
| }
|
| is (2, &nextprime ... 13).join(' '), '2 3 5 7 11 13', 'slurpy prime generator';
|
| }
|
| is (1, 2, sub {[*] @_[*-1], @_ + 1} ... 1000).join(' '), '1 2 6 24 120 720', 'slurpy factorial generator';
|
|
|
| done_testing;
|
As a list infix operator, ... takes a list on both its left and right and evaluates them as lazily as possible to produce the desired series of values. The lists are evaluated as flat lists.
The operator starts by getting the first value of righthand list. This is the only value of the right list that the ... operator is actually interested in; any additional list elements are treasured up lazily to be returned after the ... is done.
The righthand first value is considered to be the endpoint or limit of the series that is to be generated from the lefthand side by the ... operator itself.
Once we know the limit of the series, the left list is evaluated item by item, and ordinary numeric or string values are passed through unchanged. If any value in the series is eqv to the limit value, the series terminates, including that final limit value. For any value after the first lefthand value, if that value and the previous value fall on opposite sides of the limit, the series terminates without including either the limit value or the value that exceeded the limit.
If the limit is *, the series has no limit. If the limit is a closure, it will be evaluated for boolean truth on the tail of the current list, and the series will continue as long as the closure returns true. (We can't implement this till we fix all the old usages of right-hand generators, however.)
This operator would be fairly useless if it could only return the literal values on the left. The power comes from generating new values from the old ones. If the last item in the left-hand list is a closure, it is not returned, but rather it is called on the tail of the existing list to produce a new value. The arity of the closure determines how many preceding values to use as input in generating the next value in the series. For instance, counting by twos only requires a single argument:
2, { $^a + 2 } ... * # 2,4,6,8,10,12,14,16...
Generating the Fibonacci sequence takes two arguments at a time:
1, 1, { $^a + $^b } ... * # 1,1,2,3,5,8,13,21...
Any means of specifying a function will do, as long as you arrange it as a list value rather than calling it:
1, 1, &infix:<+> ... * # 1,1,2,3,5,8...
1, 1, &[+] ... * # same thing
1, 1, *+* ... * # same thing
More typically the function is unary, in which case any extra values in the lefthand list may be construed as human-readable documentation:
0,2,4, { $_ + 2 } ... 42 # all the evens up to 42
0,2,4, *+2 ... 42 # same thing
<a b c>, {.succ } ... * # same as 'a'..*
When no limit is given, the function need not be monotonic:
1, -* ... * # 1, -1, 1, -1, 1, -1...
False, &prefix:<!> ... * # False, True, False...
The function can be 0-ary as well, in which case it's okay for the closure to be the first thing:
From t/spec/S03-operators/series-arity0.t lines 4–44: (skip)
-
| # L<S03/List infix precedence/"can be 0-ary as well">
|
|
|
| plan *;
|
|
|
| # Test with Whatever limit
|
| {
|
| my @rolls = ({ (1..6).pick } ... *).[^20];
|
| is +@rolls, 20, 'Got the number of rolls we asked for';
|
| is @rolls.grep(Int).elems, 20, 'all the rolls are Ints';
|
| is @rolls.grep(1..6).elems, 20, 'all the rolls are in the Range 1..6';
|
| }
|
|
|
| # Test with exact limit
|
| {
|
| my @rolls = ({ (1..2).pick } ... 2).munch(100);
|
| ok +@rolls > 0, 'the series had at least one element...';
|
| ok +@rolls < 100, '... and the series terminated';
|
| is @rolls.grep(Int).elems, +@rolls, 'all the rolls are Ints';
|
| is @rolls.grep(2).elems, 1, 'There was exactly one 2 rolled...';
|
| is @rolls[@rolls.elems - 1], 2, '...and it was the last roll';
|
| }
|
|
|
| # Test with limit between possible values
|
| {
|
| my @rolls = ({ (1..2).pick } ... 1.5).munch(100);
|
| ok +@rolls > 0, 'the series had at least one element...';
|
| ok +@rolls < 100, '... and the series terminated';
|
| is @rolls.grep(Int).elems, +@rolls, 'all the rolls are Ints';
|
| is @rolls.grep(@rolls[0]).elems, +@rolls, 'All the rolls are the same';
|
| }
|
|
|
| # Test with limit that cannot be hit
|
| {
|
| my @rolls = ({ (1..6).pick } ... 7).munch(40);
|
| is +@rolls, 40, 'Got the number of rolls we asked for';
|
| is @rolls.grep(Int).elems, 40, 'all the rolls are Ints';
|
| is @rolls.grep(1..6).elems, 40, 'all the rolls are in the Range 1..6';
|
| }
|
|
|
|
|
| done_testing;
|
{ rand }...* # list of random numbers
The function may also be slurpy (n-ary), in which case all the preceding values are passed in (which means they must all be cached by the operator, so performance may suffer).
The arity of the function need not match the number of return values, but if they do match you may interleave unrelated sequences:
From t/spec/S03-operators/series-misc.t lines 19–27: (skip)
-
| # L<S03/List infix precedence/interleave unrelated sequences>
|
| # multiple return values
|
|
|
| is (1, 1, { $^a + 1, $^b * 2 } ... *)[^12].join(' '), '1 1 2 2 3 4 4 8 5 16 6 32', 'series of two interleaved sequences';
|
| is (1, 1, 1, { $^a + 1, $^b * 2, $^c - 1 } ... *)[^18].join(' '), '1 1 1 2 2 0 3 4 -1 4 8 -2 5 16 -3 6 32 -4', 'series of three interleaved sequences';
|
| is (1, { $^n + 1 xx $^n + 1 } ... *)[^10].join(' '), '1 2 2 3 3 3 4 4 4 4', 'series with list-returning block';
|
| is ('a', 'b', { $^a ~ 'x', $^a ~ $^b, $^b ~ 'y' } ... *)[^11].join(' '), 'a b ax ab by abx abby byy abbyx abbybyy byyy', 'series with arity < number of return values';
|
| is ('a', 'b', 'c', { $^x ~ 'x', $^y ~ 'y' ~ $^z ~ 'z' } ... *)[^9].join(' '), 'a b c ax bycz cx axybyczz byczx cxyaxybyczzz', 'series with arity > number of return values';
|
|
|
1,1,{ $^a + 1, $^b * 2 }...* # 1,1,2,2,3,4,4,8,5,16,6,32...
A series operator generated from an explicit function places no type constraints on the series other than those constraints implied by the signature of the function. If the signature of the function does not match the existing values, the series terminates.
From t/spec/S03-operators/series-misc.t lines 6–12: (skip)
-
| # L<S03/List infix precedence/constraints implied by the signature of the function>
|
|
|
| {
|
| sub f (Int $n) { $n > 3 ?? 'liftoff!' !! $n + 1 }
|
| is (1, &f ... *).join(' '), '1 2 3 liftoff!', 'series terminated by signature mismatch';
|
| }
|
|
|
If no closure is provided, and the sequence is numeric, and is obviously arithmetic or geometric (from examining its last 3 values), the appropriate function is deduced:
1, 3, 5 ... * # odd numbers
1, 2, 4 ... * # powers of 2
10,9,8 ... 0 # countdown
That is, supposing we call the last three numbers $a, $b, and $c, and then define:
$ab = $b - $a;
$bc = $c - $b;
If $ab == $bc and $ab is not zero, then we deduce an arithmetic progression determined by the function *+$ab. If $ab is zero, and the three values look like numbers, then the function is *+0. If they do not look like numbers, then the function selected is either *.succ or *.pred depending on whether $b cmp $c appears to be Increasing or Decreasing. If cmp returns Same then an identity function is assumed.
If $ab != $bc and none($a,$b,$c) == 0, then a similar calculation is done using division rather than subtraction to determine whether a geometric progression is warranted. Define:
$ab = $b / $a;
$bc = $c / $b;
If the two quotients are equal (and finite), then a geometric function of {$_ * $bc} is deduced.
If there are only two values in the list so far, $a and $b, and the difference $ab is non-zero, we assume an arithmetic progression of *+$ab. If $ab is zero, then again it depends on whether the two values look like numbers whether we use *+0 or *.succ/*.pred.
If there is only one value, we always assume incrementation via .succ. (This may be forced to .pred by examination of a limit, as specified below.) Hence these come out the same:
1 .. *
1 ... *
1,2 ... *
1,2,3 ... *
<1 2 3> ... *
Likewise, if the given value or values are not numeric, .succ is assumed, so these come out the same:
'a' .. *
'a' ... *
'a','b' ... *
'a','b','c' ... *
<a b c> ... *
If the list on the left is Nil, we use the function {Nil} to generate an infinite supply of nothing.
For intuited numeric generators that don't involve geometric sign changes, all values are assumed to be monotonically increasing or decreasing, as determined by the (up to) three values used above; if a supplied limit value is on the "wrong" side of the first of those values, Nil is returned, even though the limit value never matches, and never falls between two generated values. Examples:
From t/spec/S03-operators/series.t lines 109–116: (skip)
-
| # L<S03/List infix precedence/'limit value is on the "wrong"'>
|
| #?rakudo 5 skip "Rakudo does not handle the 1, 2 ... 0 case properly yet"
|
| is (1, 2 ... 0), Nil, 'empty increasing arithmetic series';
|
| is (1, 0 ... 2), Nil, 'empty decreasing arithmetic series';
|
| is (1, 2, 4 ... -5), Nil, 'empty increasing geometric series';
|
| is (64, 32, 16 ... 70), Nil, 'empty decreasing geometric series';
|
| is (1, 2 ... 0, 'xyzzy', 'plugh').join(' '), 'xyzzy plugh', 'series empty but for extra items';
|
|
|
my $n = 0;
1,2,4 ... $n; # (), geometric increasing
-1,-2 ... $n; # (), arithmetic decreasing
For a geometric series with sign changes, the same criterion is used, but applied only to the absolute value, and the impossibility of a limit is evaluated by whether it's inside or outside the possible range:
From t/spec/S03-operators/series.t lines 117–123: (skip)
-
| # L<S03/List infix precedence/For a geometric series with sign changes>
|
| #?rakudo 4 skip "or the 1, -2, 4 ... 1/2 case"
|
| is (1, -2, 4 ... 1/2), Nil, 'empty alternating increasing-in-magnitude geometric series';
|
| is (-64, 32, -16 ... 70), Nil, 'empty alternating decreasing-in-magnitude geometric series';
|
| is (1, -1, 1 ... 2), Nil, 'empty alternating series (1)';
|
| is (1, -1, 1 ... -2), Nil, 'empty alternating series (2)';
|
|
|
1,-2,4 ... 0 # (), geometric alternating increasing abs
1,-1/2,1/4 ... 2 # (), geometric alternating decreasing abs
But since "asymptotically approaching" is not the same as "equals", both of the following are infinite lists, as if you'd specified * for the limit rather than 0:
From t/spec/S03-operators/series.t lines 99–108: (skip)
-
| # L<S03/List infix precedence/'"asymptotically approaching" is not the same as "equals"'>
|
| # infinite series with limits
|
|
|
| is ~(1, 1/2, 1/4 ... 0).[^5].map({.perl}), '1 1/2 1/4 1/8 1/16', 'geometric series that never reaches its limit';
|
| #?rakudo todo "Not sure why this one doesn't work"
|
| is ~(1, -1/2, 1/4 ... 0).[^5].map({.perl}), '1 -1/2 1/4 -1/8 1/16', 'alternating geometric series that never reaches its limit';
|
| is (1, { 1 / ((1 / $_) + 1) } ... 0).[^5].map({.perl}).join(', '), '1, 1/2, 1/3, 1/4, 1/5', '"harmonic" series that never reaches its limit';
|
|
|
| # empty series
|
|
|
1,1/2,1/4 ... 0 # like 1,1/2,1/4 ... *
1,-1/2,1/4 ... 0 # like 1,-1/2,1/4 ... *
When an explicit limit function is used, it may choose to terminate its list by returning any false value. Since this operator is list associative, an inner function may be followed by a ... and another function to continue the list, and so on. Hence,
From t/spec/S03-operators/series-misc.t lines 32–78: (skip)
-
| # L<S03/List infix precedence/and another function to continue the list>
|
| # chained series
|
| #?rakudo emit skip_rest 'chained series NYI';
|
|
|
| is (1 ... 5 ... 10).join(' '),
|
| '1 2 3 4 5 6 7 8 9 10',
|
| 'simple chained finite arithmetic series';
|
| is infix:<...>(1; 5; 10).join(' '),
|
| '1 2 3 4 5 6 7 8 9 10',
|
| "simple chained finite arithmetic series (with 'infix:<...>')";
|
| is (1 ... 5, 10 ... 25, 50 ... 150).join(' '),
|
| '1 2 3 4 5 10 15 20 25 50 75 100 125 150',
|
| 'chained finite arithmetic series';
|
| is (1 ... 4, 8, 16 ... 64, 63, 62 ... 58).join(' '),
|
| '1 2 3 4 8 16 32 64 63 62 61 60 59 58',
|
| 'chained finite numeric series';
|
| is infix:<...>(1; 4, 8, 16; 64, 63, 62; 58).join(' '),
|
| '1 2 3 4 8 16 32 64 63 62 61 60 59 58',
|
| "chained finite numeric series (with 'infix:<...>')";
|
| is (1/4, 1/2, 1 ... 8, 9, ... *)[^10].join(' '),
|
| '1/4 1/2 1 2 4 8 9 10 11 12',
|
| 'chained infinite numeric series';
|
| is infix:<...>(1/4, 1/2, 1; 8, 9; *).join(' '),
|
| '1/4 1/2 1 2 4 8 9 10 11 12',
|
| "chained infinite numeric series (with 'infix:<...>')";
|
| is (1, 4, 7 ... 16, 16 ... *)[^8].join(' '),
|
| '1 4 7 10 13 16 16 16',
|
| 'chained eventually constant numeric series';
|
| is (0, 2 ... 7, 9 ... 14).join(' '),
|
| '0 2 4 6 7 9 11 13',
|
| 'chained arithmetic series with unreached limits';
|
| is (0, 2 ...^ 8, 11 ... 17, 18 ...^ 21).join(' '),
|
| '0 2 4 6 11 14 17 18 19 20',
|
| 'chained arithmetic series with exclusion';
|
| is (1, *+1 ... { $_ < 5 }, 5, *+10 ... { $_ < 35 }, 35, *+100 ... { $_ < 400 }).join(' '),
|
| '1 2 3 4 5 15 25 35 135 235 335',
|
| 'simple chained series with closures';
|
| is (1, { $^n*2 + 1 } ... 31, *+5 ... { $^n**2 < 2000 }, 'a', *~'z' ... { $_.chars < 6 }).join(' '),
|
| '1 3 7 15 31 36 41 a az azz azzz azzzz',
|
| 'chained series with closures';
|
| is (1, 2 ... 0, 1 ... 3).join(' '),
|
| '0 1 2 3',
|
| 'chained series with an empty subseries';
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
1, *+1 ... { $_ < 10 },
10, *+10 ... { $_ < 100 },
100, *+100 ... { $_ < 1000 }
produces
1,2,3,4,5,6,7,8,9,
10,20,30,40,50,60,70,80,90,
100,200,300,400,500,600,700,800,900
Given the heuristic when there's no closure, we can write that more simply as:
1, 2, 3 ... 9,
10, 20, 30 ... 90,
100, 200, 300 ... 900
or even just:
1, 2, 3 ...
10, 20, 30 ...
100, 200, 300 ... 900
since an exactly matching limit is returned as part of the sequence. And, in fact, since ... is list associative, and the heuristic depends only on the list to the immediate left, we can even say:
1, 2 ...
10, 20 ...
100, 200 ... 900
This works because the second ... sees only the 10,20, not the 9 before that, and likewise the third ... is blind to the 90 value. You can use parens to force one ... to be part of the list of another ... operator.
The exact function deduced depends on the direction from the final value on the left to the limit value on the right. If the limit is greater than the last value according to cmp, then comparisons are done with !after. If the limit is less, then comparisons are done with !before, and if the generator function was .succ, it is switched to .pred. Hence we have this difference:
'z' .. 'a' # null range
'z' ... 'a' # z y x ... a
Note that the sequence
1.0, *+0.2 ... 2.0
is calculated in Rat arithmetic, not Num, so the 2.0 matches exactly and terminates the sequence.
Note: the yada operator is recognized only where a term is expected. This operator may only be used where an infix is expected. If you put a comma before the ... it will be taken as a yada list operator expressing the desire to fail when the list reaches that point:
From t/spec/S03-operators/series-misc.t lines 28–31: (skip)
-
| # L<S03/List infix precedence/it will be taken as a yada>
|
|
|
| dies_ok {(1, 2, ... 3)[2]}, 'yada operator not confused for series operator';
|
|
|
1..20, ... "I only know up to 20 so far mister"
A special exception is made for any series whose endpoints are strings that happen to represent single codepoints, since the user will typically be thinking of such strings as characters rather than strings. If you say something like:
From t/spec/S03-operators/series-nonnumeric.t lines 34–84: (skip)
-
| # L<S03/List infix precedence/that happen to represent single codepoints>
|
| # character series
|
|
|
| is ('a' ... 'g').join(', '), 'a, b, c, d, e, f, g', 'finite series started with one letter';
|
| is ('a' ... *).[^7].join(', '), 'a, b, c, d, e, f, g', 'series started with one letter';
|
| is ('a', 'b' ... *).[^10].join(', '), 'a, b, c, d, e, f, g, h, i, j', 'series started with two different letters';
|
| is (<a b c> ... *).[^10].join(', '), "a, b, c, d, e, f, g, h, i, j", "character series started from array";
|
| is ('z' ... 'a').[^10].join(', '), 'z, y, x, w, v, u, t, s, r, q', 'descending series started with one letter';
|
| is (<z y> ... 'a').[^10].join(', '), 'z, y, x, w, v, u, t, s, r, q', 'descending series started with two different letters';
|
| is (<z y m> ... 'a').[^10].join(', '), 'z, y, m, l, k, j, i, h, g, f', 'descending series started with three different letters';
|
| is (<a b>, { .succ } ... *).[^7].join(', '), 'a, b, c, d, e, f, g', 'characters xand arity-1';
|
| is ('x' ... 'z').join(', '), 'x, y, z', "series ending with 'z' don't cross to two-letter strings";
|
| is ('A' ... 'z').elems, 'z'.ord - 'A'.ord + 1, "series from 'A' to 'z' is finite and of correct length";
|
| is ('α' ... 'ω').elems, 'ω'.ord - 'α'.ord + 1, "series from 'α' to 'ω' is finite and of correct length";
|
| is ('☀' ... '☕').join(''), '☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☔☕', "series from '☀' to '☕'";
|
| is ('☀' ...^ '☕').join(''), '☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☔', "exclusive series from '☀' to '☕'";
|
|
|
| # # L<S03/List infix precedence/doesn't terminate with a simple>
|
| # the tricky termination test
|
|
|
| ok ('A' ... 'ZZ').munch(1000).elems < 1000, "'A' ... 'ZZ' does not go on forever";
|
| ok ('AA' ... 'Z').munch(1000).elems < 1000, "'AA' ... 'Z' does not go on forever";
|
| ok ('ZZ' ... 'A').munch(1000).elems < 1000, "'ZZ' ... 'A' does not go on forever";
|
| ok ('Z' ... 'AA').munch(1000).elems < 1000, "'Z' ... 'AA' does not go on forever";
|
| is ('A' ...^ 'ZZ')[*-1], 'ZY', "'A' ...^ 'ZZ' omits last element";
|
|
|
| # be sure the test works as specced even for user classes
|
| {
|
| class Periodic {
|
| has Int $.val;
|
| method Str { 'P' ~ $.val }
|
| method succ { Periodic.new(val => ($.val >= 2 ?? 0 !! $.val + 1)) }
|
| method pred { Periodic.new(val => ($.val <= 0 ?? 2 !! $.val - 1)) }
|
| }
|
| multi infix:<cmp> (Periodic $x, Periodic $y) { $x.v cmp $y.v }
|
| multi infix:<cmp> (Periodic $x, Int $n) { $x.v cmp $n }
|
| multi infix:<eqv> (Periodic $x, Periodic $y) { $x.v eqv $y.v }
|
| multi infix:<eqv> (Periodic $x, Int $n) { $x.v eqv $n }
|
| my $f = { Periodic.new(val => $^v) };
|
|
|
| is ($f(0) ... 5)[^7].join(' '), 'P0 P1 P2 P0 P1 P2 P0', 'increasing periodic series';
|
| is ($f(0) ... -1)[^7].join(' '), 'P0 P2 P1 P0 P2 P1 P0', 'decreasing periodic series';
|
| is ($f(0) ... 2).join(' '), 'P0 P1 P2', 'increasing not-quite-periodic series';
|
| is ($f(2) ... 0).join(' '), 'P2 P1 P0', 'decreasing not-quite-periodic series';
|
| is ($f(0) ...^ 2).join(' '), 'P0 P1', 'exclusive increasing not-quite-periodic series';
|
| is ($f(2) ...^ 0).join(' '), 'P2 P1', 'exclusive decreasing not-quite-periodic series';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
'A' ... 'z'
"\xff" ... "\0"
it is assumed that you aren't interested in carrying within alphabetic ranges, so instead of using the ordinary .succ/.pred for strings, it uses a monotonic function that increments or decrements the underlying codepoint number like
'A', { $^prev.ord.succ.chr } ... 'z';
"\xff", { $^prev.ord.pred.chr } ... "\0";
You will note that this alternate definition doesn't change the meaning of any sequence that falls within a conventional rangechar range:
'a'...'z'
'9'...'0'
If a series is generated using a non-monotonic .succ function, it is possible for it never to reach the endpoint. The following matches:
'A' ... 'ZZ'
but since 'Z' increments to 'AA', none of these ever terminate:
'A' ... 'zz'
'A' ... '00'
'A' ... '~~'
The compiler is allowed to complain if it notices these, since if you really want the infinite list you can always write:
'A' ... *
To preserve Perl 5 semantics, you'd need something like:
'A' ... -> $old,$new { $old ne $endpoint and $new.chars <= 1; }
But since lists are lazy in Perl 6, we don't try to protect the user this way.
The astute reader will note that
'A' ... 'ZZ'
doesn't terminate with a simple !after test either. The actual function used is something like:
'A', *.succ ... -> $old,$new { $old ne 'ZZ' and $new !after 'ZZ'; }
Likewise, since Z comes after A:
'ZZ' ... 'AA'
uses the function:
'ZZ', *.pred ... -> $old,$new { $old ne 'AA' and $new !before 'AA'; }
For purposes of deciding when to terminate the eager part of a 'mostly eager' list, any series that terminates with an exact value (or that starts another series with exact values) is considered finite, as is any series that has an explicit ending closure. However, any series that ends * is considered to be of unknowable length (even if extended with a closure that has internal logic to terminate). However, by the definition of "mostly eager" in S07, the implementation may be able to determine that such a sequence is finite by conjectural evaluation; such workahead cannot, of course, prove that a sequence is infinite without running a Very Long Time. Note also that, by using the form that specifies both a closure and a final value, it is possible to write series that appears to be finite but that never actually reaches its final value before resources are exhausted; such a series will be treated as finite, but eventually come to grief:
@a = 1, *+0.00000000000000000000000000000000000001 ... 2; # heat death
Much like the ..^ range operator, there is an alternate form of the operator that excludes the limit if it happens to match exactly:
From t/spec/S03-operators/series.t lines 124–145: (skip)
-
| # L<S03/List infix precedence/excludes the limit if it happens to match exactly>
|
| # excluded limits via "...^"
|
| #?rakudo skip '...^ NYI'
|
| {
|
| is (1 ...^ 5).join(', '), '1, 2, 3, 4', 'exclusive series';
|
| is (1 ...^ -3).join(', '), '1, 0, -1, -2', 'exclusive decreasing series';
|
| is (1 ...^ 5.5).join(', '), '1, 2, 3, 4, 5', "exclusive series that couldn't hit its limit anyway";
|
| is (1, 3, 9 ...^ 81).join(', '), '1, 3, 9, 27', 'exclusive geometric series';
|
| is (81, 27, 9 ...^ 2).join(', '), '81, 27, 9, 3', "exclusive decreasing geometric series that couldn't hit its limit anyway";
|
| is (2, -4, 8 ...^ 32).join(', '), '2, -4, 8, -16', 'exclusive alternating geometric series';
|
| is (2, -4, 8 ...^ -32).join(', '), '2, -4, 8, -16, 32', 'exclusive alternating geometric series (not an exact match)';
|
| is (1, { $_ + 2 } ...^ 9).join(', '), '1, 3, 5, 7', 'exclusive series with closure';
|
| is (1 ...^ 1), Nil, 'empty exclusive series';
|
| is (1, 1 ...^ 1), Nil, 'empty exclusive constant series';
|
| is (1, 2 ...^ 0), Nil, 'empty exclusive arithmetic series';
|
| is (1, 2 ...^ 0, 'xyzzy', 'plugh').join(' '), 'xyzzy plugh', 'exclusive series empty but for extra items';
|
| is ~(1 ...^ 0), '1', 'singleton exclusive series';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
0,1,2 ...^ 100,42 # same as ^100,42
There is no corresponding exclusion on the left side. The compiler may complain if it sees anything on the right that is not a literal:
0,1,2 ...^ *
0,1,2 ...^ {$_ < 100}
infix:<E>, test for EMPTY iterator
Returns the list on the left unless any of them are EMPTY, in which case it executes the thunk on the right. Typically used in a loop with take:
loop {
take @iterators>>.get E last;
}
Many of these operators return a list of Parcels, which depending on context may or may not flatten them all out into one flat list. The default is to flatten, but see the contextualizers below.
infix:<=>, list assignment
@array = 1,2,3;
With compound targets, performs list assignment. The right side is looser than comma. You might be wondering why we've classified this as a prefix operator when its token name is infix:<=>. That's because you can view the left side as a special syntax for a prefix listop, much as if you'd said:
@array.assign: 1,2,3
However, the tokener classifies it as infix because it sees it when it's expecting an infix operator. Assignments in general are treated more like retroactive macros, since their meaning depends greatly on what is on the left, especially if what is on the left is a declarator of some sort. We even call some of them pseudo-assignments, but they're all a bit pseudo insofar as we have to figure out whether the left side is a list or a scalar destination.
In any case, list assignment is defined to be arbitrarily lazy, insofar as it basically does the obvious copying as long as there are scalar destinations on the left or already-computed values on the right. However, many list lvalues end with an array destination (where assignment directly to an array can be considered a degenerate case). When copying into an array destination, the list assignment is "mostly eager"; it requests the list to evaluate its leading iterators (and values) to the extent that they are known to be finite, and then suspend, returning the known values. The assignment then copies the known values into the array. (These two steps might actually be interleaved depending on how the iterator API ends up being defined.) It then sets up the array to be self-extending by using the remainder of the list as the "specs" for the array's remaining values, to be reified on demand. Hence it is legal to say:
@natural = 0..*;
(Note that when we say that an iterator in list context suspends, it is not required to suspend immediately. When the scheduler is running an iterator, it may choose to precompute values in batches if it thinks that approach will increase throughput. This is likely to be the case on single-core architectures with heavy context switching, and may very well be the case even on manycore CPU architectures when there are more iterators than cores, such that cores may still have to do context switching. In any case, this is all more-or-less transparent to the user because in the abstract the list is all there, even if it hasn't been entirely computed yet.)
Though elements may be reified into an array on demand, they act like ordinary array elements both before and after reification, as far as the user is concerned. These elements may be written to if the underlying container type supports it:
@unnatural = 0..*;
@unnatural[42] = "Life, the Universe, and Everything";
Note that, unlike assignment, binding replaces the container, so the following fails because a range object cannot be subscripted:
@natural := 0..*; # bind a Range object
@natural[42] = "Life, the Universe, and Everything"; # FAILS
but this succeeds:
@unnatural := [0..*]; # bind an Array object
@unnatural[42] = "Life, the Universe, and Everything"; # ok
It is erroneous to make use of any side effects of reification, such as movement of a file pointer, since different implementations may have different batch semantics, and in any case the unreified part of the list already "belongs" to the array.
When a self-extending array is asked for its count of elements, it is allowed to return +Inf without blowing up if it can determine by inspection that its unreified parts contain any infinite lists. If it cannot determine this, it is allowed to use all your memory, and then some. :)
Assignment to a hash is not lazy (probably).
- Normal listops
print push say join split substr open etc.
- Listop forms of junctional operators
any all one none
- Exception generators
fail "Division by zero"
die System::Error(ENOSPC,"Drive $d seems to be full");
warn "Can't open file: $!"
- Stubby exception generators
...
!!! "fill this in later, Dave"
??? "oops in $?CLASS"
The ... operator is the "yada, yada, yada" list operator, which among other things is used as the body in function prototypes. It complains bitterly (by calling fail) if it is ever executed. Variant ??? calls warn, and !!! calls die. The argument is optional, but if provided, is passed onto the fail, warn, or die. Otherwise the system will make up a message for you based on the context, indicating that you tried to execute something that is stubbed out. (This message differs from what fail, warn, and die would say by default, since the latter operators typically point out bad data or programming rather than just an incomplete design.)
- Reduce operators
[+] [*] [<] [\+] [\*] etc.
See "Reduction operators" below.
- Sigils as coercions to roles
Sigil Alpha variant
----- -------------
$ Scalar
@ Positional (or Iterable?)
% Associative
& Callable
Note that, since these are coercions to roles, they are allowed to return any actual type that does the role in question.
Unless applied directly to a scalar variable, as in @$a, these may only be applied with explicit parens around an argument that is processed as a bare Parcel object, not a flattening list:
$(1,2 Z 3,4) # Scalar((1,3),(2,4))
@(1,2 Z 3,4) # ((1,3),(2,4))
%(1,2 Z 3,4) # PairSeq(1 => 3, 2 => 4)
$(1,2 X 3,4) # Scalar((1,3),(1,4),(2,3),(2,4))
@(1,2 X 3,4) # ((1,3),(1,4),(2,3),(2,4))
(Note, internal parens indicate nested Parcel structure here, since there is no flattening.)
Since a Parcel with one argument is transparent, there can be no difference between the meaning of @($a) and @$a.
- The
item contextualizer
From t/spec/S03-operators/context.t lines 34–66: (skip)
-
| # L<S03/List prefix precedence/The item contextualizer>
|
| # L<S02/Lists/To force a non-flattening item context>
|
|
|
| {
|
| my $a = 3;
|
| my $b = 2;
|
|
|
| is(~(item $a).WHAT, ~$a.WHAT, '(item $a).WHAT matches $a.WHAT');
|
| is((item $a), $a, 'item $a is just $a');
|
| is(item($a), $a, 'item($a) is just $a');
|
| is($($a), $a, '$($a) is just $a');
|
|
|
| isa_ok((item $a, $b).WHAT, Seq, '(item $a, $b) makes a Seq');
|
| isa_ok(item($a, $b).WHAT, Seq, 'item $a, $b makes a Seq');
|
| isa_ok($($a, $b).WHAT, Seq, '$ $a, $b makes a Seq');
|
| my @array = ($a, $b);
|
| is((item $a, $b), @array, 'item($a, $b) is the same as <<$a $b>> in an array');
|
| }
|
|
|
| #?rakudo skip 'loops'
|
| {
|
| # Most of these tests pass in Rakudo, but we must compare with
|
| # eqv instead of eq, since the order of hashes is not guaranteed
|
| # with eq. eqv does guarantee the order.
|
| # also, we assign to a hash since rakudo does not recognize
|
| # {} as a hash constructor and () does not make a hash
|
| ok(%('a', 1, 'b', 2) eqv {a => 1, b => 2}, '%(values) builds a hash');
|
| ok(hash('a', 1, 'b', 2) eqv {a => 1, b => 2}, 'hash(values) builds a hash');
|
| ok((hash 'a', 1, 'b', 2) eqv {a => 1, b => 2}, 'hash values builds a hash');
|
| #?rakudo todo 'hash of one element dies'
|
| eval_dies_ok('hash("a")', 'building a hash of one item fails');
|
| }
|
|
|
item foo()
The new name for Perl 5's scalar contextualizer. Equivalent to $(...) (except that empty $() means $<? // Str($/)>, while empty item() yields Failure). We still call the values scalars, and talk about "scalar operators", but scalar operators are those that put their arguments into item context.
If given a list, this function makes a Seq object from it. The function is agnostic about any Parcel embedded in such a sequence, and any contextual decisions will be deferred until subsequent use of the contents.
Note that this parses as a list operator, not a unary prefix operator, since you'd generally want it for converting a list to a sequence object. (Single items don't need to be converted to items.) Note, however, that it does no flattening of its list items:
@x = slice(item (1,2),(3,4)) # @x eqv [[1,2],[3,4]]
- The
list contextualizer
From t/spec/S03-operators/context.t lines 7–33: (skip)
-
| # L<S03/List prefix precedence/The list contextualizer>
|
|
|
| {
|
| my $a = 3;
|
| my $b = 2;
|
| my $c = 1;
|
|
|
| # I'm not sure that smart matching is the best operation for comparison here
|
| # There might be a more specific way to check that prevents false matching
|
| isa_ok(list($a).WHAT, List, 'list(values) returns nothing more than a List');
|
| isa_ok(@($a).WHAT, List, '@(values) returns nothing more than a List');
|
| isa_ok((list $a).WHAT, List, '(list values) returns nothing more than a List');
|
|
|
| # These are all no-ops but still need to work correctly
|
| isa_ok(list($a, $b, $c).WHAT, List, 'list(values) returns nothing more than a List');
|
| isa_ok(@($a, $b, $c).WHAT, List, '@(values) returns nothing more than a List');
|
| isa_ok((list $a, $b, $c).WHAT, List, '(list values) returns nothing more than a List');
|
| is((list $a, $b, $c), ($a, $b, $c), 'list($a, $b, $c) is ($a, $b, $c)');
|
| is(@($a, $b, $c), ($a, $b, $c), '@($a, $b, $c) is ($a, $b, $c)');
|
|
|
| # Test the only difference between @() and list()
|
| is(list(), (), 'list() should return an empty list');
|
| 'foo' ~~ /oo/; # run a regex so we have $/ below
|
| #?rakudo skip '@()'
|
| is(@(), @($/), '@() should be the same as @($/)');
|
| }
|
|
|
list foo()
Forces the subsequent expression to be evaluated in list context. Any flattening happens lazily.
- The
flat contextualizer
flat foo()
Forces the subsequent expression to be evaluated in a flattening list context. The result will be recursively flattened, i.e., contain no embedded Parcel objects.
- The
slice contextualizer
slice foo()
Forces the subsequent expression to be evaluated in slice context. (Slices are considered to be potentially multidimensional in Perl 6.) A list of Parcels will be transformed into a list of Seqs. (Seq objects do not autoflatten themselves in a list unless explicitly dereferenced.)
- The
hash contextualizer
hash foo()
Forces the subsequent expression to be evaluated in hash context. The expression is evaluated in list context (flattening any Parcels), then a hash will be created from the list, taken as a list of Pairs. (Any element in the list that is not a Pair will pretend to be a key and grab the next value in the list as its value.) Equivalent to %(...) (except that empty %() means %($/), while empty hash() means an empty hash).
infix:<and>, short-circuit and
$a and $b and $c ...
Returns the first argument that evaluates to false, otherwise returns the result of the last argument. In list context forces a false return to mean (). See && above for high-precedence version.
infix:<andthen>, proceed on success
test1() andthen test2() andthen test3() ...
Returns the first argument whose evaluation indicates failure (that is, if the result is undefined). Otherwise it evaluates and returns the right argument.
If the right side is a block or pointy block, the result of the left side is bound to any arguments of the block. If the right side is not a block, a block scope is assumed around the right side, and the result of the left side is implicitly bound to $_ for the scope of the right side. That is,
test1() andthen test2()
is equivalent to
test1() andthen -> $_ { test2() }
There is no corresponding high-precedence version.
infix:<or>, short-circuit inclusive or
From t/spec/S02-builtin_data_types/parsing-bool.t lines 7–17: (skip)
-
| # L<S03/Loose or precedence/"infix:<or>, short-circuit inclusive or">
|
| # L<S02/Immutable types/"Perl boolean">
|
|
|
| is (try { 42 or Bool::False }), 42, "Bool::False as RHS";
|
| #?pugs todo 'parsing'
|
| is (try { Bool::False or 42 }), 42, "Bool::False as LHS";
|
|
|
| is (try { 42 or False }), 42, "False as RHS";
|
| is (try { False or 42 }), 42, "False as LHS";
|
|
|
| # vim: ft=perl6
|
$a or $b or $c ...
Returns the first argument that evaluates to true, otherwise returns the result of the last argument. In list context forces a false return to mean (), or Nil. See || above for high-precedence version.
infix:<xor>, exclusive or
$a xor $b xor $c ...
Returns the true argument if there is one (and only one). Returns Bool::False if all arguments are false or if more than one argument is true. In list context forces a false return to mean (), or Nil. See ^^ above for high-precedence version.
infix:<orelse>, proceed on failure
test1() orelse test2() orelse test3() ...
Returns the first argument that evaluates successfully (that is, if the result is defined). Otherwise returns the result of the right argument.
If the right side is a block or pointy block, the result of the left side is bound to any arguments of the block. If the right side is not a block, a block scope is assumed around the right side, and the result of the left side is implicitly bound to $! for the scope of the right side. That is,
test1() orelse test2()
is equivalent to
test1() orelse -> $! { test2() }
(The high-precedence // operator is similar, but does not set $! or treat blocks specially.)
As with terms, terminators are not really a precedence level, but looser than the loosest precedence level. They all have the effect of terminating any operator precedence parsing and returning a complete expression to the main parser. They don't care what state the operator precedence parser is in. If the parser is currently expecting a term and the final operator in the expression can't deal with a nullterm, then it's a syntax error. (Notably, the comma operator and many prefix list operators can handle a nullterm.)
- Semicolon: ;
$x = 1; $y = 2;
The context determines how the expressions terminated by semicolon are interpreted. At statement level they are statements. Within a bracketing construct they are interpreted as lists of Parcels, which in slice context will be treated as the multiple dimensions of a multidimensional slice. (Other contexts may have other interpretations or disallow semicolons entirely.)
- Feed operators: <==, ==>, <<==, ==>>
source() ==> filter() ==> sink()
The forms with the double angle append rather than clobber the sink's todo list. The ==>> form always looks ahead for an appropriate target to append to, either the final sink in the chain, or the next filter stage with an explicit @(*) or @(**) target. This means you can stack multiple feeds onto one filter command:
source1() ==>>
source2() ==>>
source3() ==>>
filter(@(*)) ==> sink()
Similar semantics apply to <<== except it looks backward for an appropriate target to append to.
- Control block: <ws>{...}
When a block occurs after whitespace where an infix is expected, it is interpreated as a control block for a statement control construct. (If there is no whitespace, it is a subscript, and if it is where a term is expected, it's just a bare closure.) If there is no statement looking for such a block currently, it is a syntax error.
- Statement modifiers: if, unless, while, until, for
Statement modifiers terminate one expression and start another.
- Any unexpected ), ], } at this level.
Calls into the operator precedence parser may be parameterized to recognize additional terminators, but right brackets of any sort (except angles) are automatically included in the set of terminators as tokens of length one. (An infix of longer length could conceivably start with one of these characters, and would be recognized under the longest-token rule and continue the expression, but this practice is discouraged. It would be better to use Unicode for your weird operator.) Angle brackets are exempted so that they can form hyperoperators (see "Hyper operators").
- A block-final } at the end of the line terminates the current expression. A block within an argument list terminates the argument list unless followed by the comma operator.
From t/spec/S03-operators/scalar-assign.t lines 8–28: (skip)
-
| # L<S03/Changes to Perl 5 operators>
|
|
|
| {
|
| my $x = 15;
|
| my $y = 1;
|
| ($x = $y) = 5;
|
| is $x, 5, 'order of assignment respected (1)';
|
| is $y, 1, 'order of assignment respected (2)';
|
| $x = $y = 7;
|
| is $y, 7, 'assignment is right-associative';
|
| }
|
|
|
| # From p5 "perldoc perlop"
|
| {
|
| my $x = 1;
|
| ($x += 2) *= 3;
|
| is $x, 9, 'lvalue expressions are only evaluated once';
|
| }
|
|
|
|
|
| # vim: ft=perl6
|
Several operators have been given new names to increase clarity and better Huffman-code the language, while others have changed precedence.
- Perl 5's
${...}, @{...}, %{...}, etc. dereferencing forms are now $(...), @(...), %(...), etc. instead. (Use of the Perl 5 curly forms will result in an error message pointing the user to the new forms.) As in Perl 5, the parens may be dropped when dereferencing a scalar variable.
From t/spec/S03-operators/context.t lines 67–88: (skip)
-
| # L<S03/"Changes to Perl 5 operators"/Perl 5's ${...}, @{...}, %{...}, etc>
|
| # ^ non-breaking space
|
| # Deprecated P5 dereferencing operators:
|
| {
|
| my $scalar = 'abcd';
|
| eval_dies_ok('${$scalar}', 'Perl 5 form of ${$scalar} dies');
|
|
|
| my $array = [1, 2, 3];
|
| eval_dies_ok('@{$array}', 'Perl 5 form of @{$array} dies');
|
|
|
| my $hash = {a => 1, b => 2, c => 3};
|
| eval_dies_ok('%{$hash}', 'Perl 5 form of %{$hash} dies');
|
| }
|
|
|
| eval_dies_ok('$', 'Anonymous variable outside of declaration');
|
| eval_dies_ok('@', 'Anonymous variable outside of declaration');
|
| eval_dies_ok('%', 'Anonymous variable outside of declaration');
|
| eval_dies_ok('&', 'Anonymous variable outside of declaration');
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
-> becomes ., like the rest of the world uses. There is a pseudo postfix:['->'] operator that produces a compile-time error reminding Perl 5 users to use dot instead. (The "pointy block" use of -> in Perl 6 requires preceding whitespace when the arrow could be confused with a postfix, that is, when an infix is expected. Preceding whitespace is not required in term position.)
From t/spec/S12-methods/chaining.t lines 7–61: (skip)
-
| # L<S03/"Changes to Perl 5 operators"/"-> becomes .">
|
|
|
| class Foo {
|
| has $.num;
|
|
|
| method bar ($self: $num) returns Foo {
|
| $!num = $num;
|
| return $self;
|
| }
|
|
|
| method baz ($self: $num) returns Foo {
|
| $!num += $num;
|
| return $self;
|
| }
|
| }
|
|
|
| my $foo = Foo.new(:num<10>);
|
| isa_ok($foo, Foo);
|
|
|
| # do some sanity checking to make sure it does
|
| # all that we expect it too first.
|
|
|
| is($foo.num(), 10, '... got the right num value');
|
|
|
| my $_foo1 = $foo.bar(20);
|
| isa_ok($_foo1, Foo);
|
| ok($_foo1 === $foo, '... $_foo1 and $foo are the same instances');
|
|
|
| is($foo.num(), 20, '... got the right num value');
|
|
|
| my $_foo2 = $foo.baz(20);
|
| isa_ok($_foo2, Foo);
|
| ok( ([===]($foo, $_foo2, $_foo1)), '... $_foo1, $_foo2 and $foo are the same instances');
|
|
|
| is($foo.num(), 40, '... got the right num value');
|
|
|
| # now lets try it with chained methods ...
|
|
|
| my $_foo3;
|
| lives_ok {
|
| $_foo3 = $foo.bar(10).baz(5);
|
| }, '... method chaining works';
|
|
|
| isa_ok($_foo3, Foo);
|
| ok( ([===]($_foo3, $_foo2, $_foo1, $foo)),
|
| '... $_foo3, $_foo1, $_foo2 and $foo are the same instances');
|
|
|
| is($foo.num(), 15, '... got the right num value');
|
|
|
| # test attribute accessors, too
|
| is($foo.baz(7).baz(6).num, 28, 'chained an auto-generated accessor');
|
|
|
| eval_dies_ok('Foo->new', 'Perl 5 -> is dead (class constructor)');
|
| eval_dies_ok('$foo->num', 'Perl 5 -> is dead (method call)');
|
|
|
From t/spec/S12-methods/chaining.t lines 62–78: (skip)
-
| # L<S03/"Changes to Perl 5 operators"/"-> becomes .">
|
| # L<S12/"Open vs Closed Classes"/"though you have to be explicit">
|
| {
|
| # (A => (B => Mu)) => (C => Mu))
|
| # ((A B) C)
|
|
|
| my $cons = [=>] ( [=>] <A B>, Mu ), <C>, Mu;
|
|
|
| ## Hmm. Works with the latest release of Pugs (6.2.12 (r13256))
|
| ## Leaving this in as something that once didn't work (6.2.12 CPAN)
|
|
|
| my $p = $cons.key;
|
| ok( $cons.key.key =:= $p.key, 'chaining through temp variable' );
|
| ok( $cons.key.key =:= $cons.key.key, 'chaining through Any return');
|
| }
|
|
|
| # vim: ft=perl6
|
- The string concatenation
. becomes ~. Think of it as "stitching" the two ends of its arguments together. String append is likewise ~=.
From t/spec/S32-str/append.t lines 6–31: (skip)
-
| # L<S03/Changes to Perl 5 operators/string concatenation becomes stitching>
|
|
|
| plan 7;
|
|
|
| # Again, mostly stolen from Perl 5
|
|
|
| my $a = 'ab' ~ 'c';
|
| is($a, 'abc', '~ two literals correctly');
|
|
|
| my $b = 'def';
|
|
|
| my $c = $a ~ $b;
|
| is($c, 'abcdef', '~ two variables correctly');
|
|
|
| $c ~= "xyz";
|
| is($c, 'abcdefxyz', '~= a literal string correctly');
|
|
|
| my $d = $a;
|
| $d ~= $b;
|
| is($d, 'abcdef', '~= variable correctly');
|
|
|
| is('' ~ '', '', 'Concatenating two empty strings');
|
| is($d ~ '', $d, 'Concatenente non-empty and empty string');
|
| is('' ~ $d, $d, 'Concatenente empty and non-empty string');
|
|
|
| # vim: ft=perl6
|
- The filetest operators are gone. We now use a
Pair as a pattern that calls an object's method:
From t/spec/S16-filehandles/filetest.t lines 21–121: (skip)
-
| # L<S03/Changes to Perl 5 operators/The filetest operators are gone.>
|
| # old: L<S16/Filehandles, files, and directories/A file test, where X is one of the letters listed below.>
|
|
|
| dies_ok { 't' ~~ :d }, 'file test from before spec revision 27503 is error';
|
|
|
| # Basic tests
|
| ok 't'.IO ~~ :d, "~~:d returns true on directories";
|
| lives_ok { 'non_existing_dir'.IO ~~ :d },
|
| 'can :d-test against non-existing dir and live';
|
| ok !('non_existing_dir'.IO ~~ :d ),
|
| 'can :d-test against non-existing dir and return false';
|
| ok $*PROGRAM_NAME.IO ~~ :f, "~~:f returns true on files";
|
| ok $*PROGRAM_NAME.IO ~~ :e, "~~:e returns true on files";
|
| ok 't'.IO ~~ :e, "~~:e returns true on directories";
|
| #?rakudo 2 skip ':r, :w'
|
| ok $*PROGRAM_NAME.IO ~~ :r, "~~:r returns true on readable files";
|
| ok $*PROGRAM_NAME.IO ~~ :w, "~~:w returns true on writable files";
|
|
|
| if $*OS eq any <MSWin32 mingw msys cygwin> {
|
| skip 2, "win32 doesn't have ~~:x";
|
| } else {
|
| if $*EXECUTABLE_NAME.IO ~~ :e {
|
| #?rakudo skip ':x'
|
| ok $*EXECUTABLE_NAME.IO ~~ :x, "~~:x returns true on executable files";
|
| }
|
| else {
|
| skip 1, "'$*EXECUTABLE_NAME' is not present (interactive mode?)";
|
| }
|
| #?rakudo skip ':x'
|
| ok 't'.IO ~~ :x, "~~:x returns true on cwd()able directories";
|
| }
|
|
|
| #?rakudo 999 skip 'other file test operations'
|
| ok not "t".IO ~~ :f, "~~:f returns false on directories";
|
| ok "t".IO ~~ :r, "~~:r returns true on a readable directory";
|
|
|
| ok 'doesnotexist'.IO !~~ :d, "~~:d returns false on non-existent directories";
|
| ok 'doesnotexist'.IO !~~ :r, "~~:r returns false on non-existent directories";
|
| ok 'doesnotexist'.IO !~~ :w, "~~:w returns false on non-existent directories";
|
| ok 'doesnotexist'.IO !~~ :x, "~~:x returns false on non-existent directories";
|
| ok 'doesnotexist'.IO !~~ :f, "~~:f returns false on non-existent directories";
|
|
|
| ok not 'doesnotexist.t'.IO ~~ :f, "~~:f returns false on non-existent files";
|
| ok not 'doesnotexist.t'.IO ~~ :r, "~~:r returns false on non-existent files";
|
| ok not 'doesnotexist.t'.IO ~~ :w, "~~:w returns false on non-existent files";
|
| ok not 'doesnotexist.t'.IO ~~ :x, "~~:x returns false on non-existent files";
|
| ok not 'doesnotexist.t'.IO ~~ :f, "~~:f returns false on non-existent files";
|
|
|
| # XXX - Without parens, $*PROGRAM_NAME ~~ :s>42 is chaincomp.
|
| ok(($*PROGRAM_NAME~~:s) > 42, "~~:s returns size on existent files");
|
|
|
| ok not "doesnotexist.t".IO ~~ :s, "~~:s returns false on non-existent files";
|
|
|
| ok not $*PROGRAM_NAME.IO ~~ :z, "~~:z returns false on existent files";
|
| ok not "doesnotexist.t".IO ~~ :z, "~~:z returns false on non-existent files";
|
| ok not "t".IO ~~ :z, "~~:z returns false on directories";
|
|
|
| my $fh = open("empty_file", :w);
|
| close $fh;
|
| ok "empty_file".IO ~~ :z, "~~:z returns true for an empty file";
|
| unlink "empty_file";
|
|
|
| if $*OS eq any <MSWin32 mingw msys cygwin> {
|
| skip 9, "~~:M/~~:C/~~:A not working on Win32 yet"
|
| }
|
| else {
|
| my $fn = 'test_file_filetest_t';
|
| my $fh = open($fn, :w);
|
| close $fh;
|
| sleep 1; # just to make sure
|
| #?rakudo 3 skip ':M, :C, :A'
|
| ok ($fn.IO ~~ :M) < 0, "~~:M works on new file";
|
| ok ($fn.IO ~~ :C) < 0, "~~:C works on new file";
|
| ok ($fn.IO ~~ :A) < 0, "~~:A works on new file";
|
| unlink $fn;
|
|
|
| if (! "README" ~~ :f) {
|
| skip 3, "no file README";
|
| } else {
|
| #?rakudo 3 skip ':M, :C, :A'
|
| ok ("README".IO ~~ :M) > 0, "~~:M works on existing file";
|
| ok ("README".IO ~~ :C) > 0, "~~:C works on existing file";
|
| ok ("README".IO ~~ :A) > 0, "~~:A works on existing file";
|
| }
|
|
|
| #?rakudo 3 skip ':M, :C, :A'
|
| ok not "xyzzy".IO ~~ :M, "~~:M returns undefined when no file";
|
| ok not "xyzzy".IO ~~ :C, "~~:C returns undefined when no file";
|
| ok not "xyzzy".IO ~~ :A, "~~:A returns undefined when no file";
|
| }
|
|
|
| # potential parsing difficulties (pugs)
|
| {
|
| sub f { return 8; }
|
|
|
| is(f($*PROGRAM_NAME), 8, "f(...) works");
|
| is(- f($*PROGRAM_NAME), -8, "- f(...) does not call the ~~:f filetest");
|
| }
|
|
|
|
|
| # vim: ft=perl6
|
if $filename.IO ~~ :e { say "exists" }
is the same as
if so $filename.IO.e { say "exists" }
Likewise
if $filename.IO ~~ :!e { say "exists" }
is the same as
if not $filename.IO.e { say "exists" }
The 1st form actually translates to the latter form, so the object's class decides how to dispatch methods. It just happens that the IO role defaults to the expected filetest semantics, but $regex.i might tell you whether the regex is case insensitive, for instance. Likewise, you can test anything for definedness or undefinedness:
$obj ~~ :defined
$obj ~~ :!defined
Using the pattern form, multiple tests may be combined via junctions:
given $handle {
when :r & :w & :x {...}
when :!w | :!x {...}
when * {...}
}
When adverbial pairs are stacked into one term, it is assumed they are ANDed together, so
when :r :w :x
is equivalent to either of:
when :r & :w & :x
when all(:r,:w,:x)
The pair forms are useful only for boolean tests because the method's value is evaluated as a Bool, so the method form must be used for any numeric-based tests:
if stat($filename).s > 1024 {...}
However, these still work:
given $fh {
when :s {...} # file has size > 0
when :!s {...} # file size == 0
}
One advantage of the method form is that it can be used in places that require tighter precedence than ~~ provides
sort { $^a.M <=> $^b.M }, @files».IO
though that's a silly example since you could just write:
sort { .M }, @files».IO
But that demonstrates the other advantage of the method form, which is that it allows the "unary dot" syntax to test the current topic.
Unlike in earlier versions of Perl 6, these filetest methods do not return stat buffers, but simple scalars of type Bool, Int, or Num.
In general, the user need not worry about caching the stat buffer when a filename is queried. The stat buffer will automatically be reused if the same object has recently been queried, where "recently" is defined as less than a second or so. If this is a concern, an explicit stat() or lstat() may be used to return an explicit IO object that will not be subject to timeout, and may be tested repeatedly just as a filename or handle can. An IO object has a .file method that can be queried for its filename (if known).
(Inadvertent use of the Perl 5 forms will normally result in treatment as a negated postdeclared subroutine, which is likely to produce an error message at the end of compilation.)
- All postfix operators that do not start with a dot also have an alternate form that does. (The converse does not hold--just because you can write
x().foo doesn't mean you can write x()foo. Likewise the ability to say $x.'foo' does not imply that $x'foo' will work.)
The postfix interpretation of an operator may be overridden by use of a quoted method call, which calls the prefix form instead. So x().! is always the postfix operator, but x().'!' will always call !x(). In particular, you can say things like $array.'@'. This also includes any operator that would look like something with a special meaning if used after the method-calling dot. For example, If you defined a prefix:<=>, and you wanted to write it using the method-call syntax instead of =$object, the parser would take $object.= as the mutation syntax (see S12, "Mutating methods"). Writing $object.'=' will call your prefix operator.
- Unary
~ now imposes a string (Stringy) context on its argument, and + imposes a numeric (Numeric) context (as opposed to being a no-op in Perl 5). Along the same lines, ? imposes a boolean (Bool) context, and the | unary operator imposes a function-arguments (Parcel or Capture) context on its argument. Unary sigils are allowed when followed by a $ sigil on a scalar variable; they impose the container context implied by their sigil. As with Perl 5, however, $$foo[bar] parses as ( $($foo) )[bar], so you need $($foo[bar]) to mean the other way. In other words, sigils are not really parsed as operators, and you must use the parenthetical form for anything complicated.
From t/spec/S03-operators/context-forcers.t lines 108–123: (skip)
-
| # L<S03/Changes to Perl 5 operators/Unary ~ string context>
|
| # string context
|
| {
|
| my $a = 10.500000;
|
| ok(~$a ~~ Stringy, 'it is forced into a Str');
|
| is(~$a, '10.5', 'forced into string context');
|
|
|
| my $b = -100;
|
| ok(~$b ~~ Stringy, 'it is forced into a Str');
|
| is(~$b, '-100', 'forced into string context');
|
|
|
| my $c = -100.1010;
|
| ok(~$c ~~ Stringy, 'it is forced into a Str');
|
| is(~$c, '-100.101', 'forced into string context');
|
| }
|
|
|
From t/spec/S03-operators/context-forcers.t lines 125–152: (skip)
-
| # L<S03/Changes to Perl 5 operators/"?" imposes boolean context>
|
| # boolean context
|
| {
|
| my $a = '';
|
| isa_ok(?$a, Bool, 'it is forced into a Bool');
|
| ok(!(?$a), 'it is forced into boolean context');
|
|
|
| my $b = 'This will be true';
|
| isa_ok(?$b, Bool, 'it is forced into a Bool');
|
| ok(?$b, 'it is forced into boolean context');
|
|
|
| my $c = 0;
|
| isa_ok(?$c, Bool, 'it is forced into a Bool');
|
| ok(!(?$c), 'it is forced into boolean context');
|
|
|
| my $d = 1;
|
| isa_ok(?$d, Bool, 'it is forced into a Bool');
|
| ok(?$d, 'it is forced into boolean context');
|
| }
|
|
|
| #?rakudo skip 'is context'
|
| {
|
| my $arrayref is context = list(1,2,3);
|
| my $boo is context = 37;
|
| ok eval_elsewhere('?(@$+arrayref)'), '?(@$arrayref) syntax works';
|
| ok eval_elsewhere('?(@($+arrayref))'), '?(@($arrayref)) syntax works';
|
| }
|
|
|
From t/spec/S03-operators/numeric-context.t lines 5–88: (skip)
-
| # L<S03/Changes to Perl 5 operators/"imposes a numeric">
|
|
|
| # Per Larry: L<http://www.nntp.perl.org/group/perl.perl6.compiler/1134>
|
| is(+'0012', 12, "+'0012' is 12");
|
| is(+'0000', 0, "+'0000' is 0");
|
| is(+'000a', 0, "+'000a' is 0 (illegal number)");
|
|
|
| is(+'1900', 1900, "+'1900' is 1900");
|
| #?rakudo todo "+'1900' is a Num in Rakudo-ng"
|
| isa_ok(+'1900', Int, "+'1900' is an Int");
|
| #?rakudo 2 skip "Rat in string not supported yet in Rakudo-ng"
|
| is(+'3/2', 3/2, "+'3/2' is 3/2");
|
| isa_ok(+'3/2', Rat, "+'3/2' is a Rat");
|
| is(+'1.9e3', 1900, "+'1.9e3' is 1900");
|
| isa_ok(+'1.9e3', Num, "+'1.9e3' is a Num");
|
|
|
| is(+'Inf', Inf, "+'Inf' is Inf");
|
| is(+'Info', 0, "+'Info' is 0");
|
| is(+'-Inf', -Inf, "+'-Inf' is -Inf");
|
| is(+'-Info', 0, "+'-Info' is 0");
|
| is(+'NaN', NaN, "+'NaN' is NaN");
|
| is(+'NaNa', 0, "+'NaNa' is 0");
|
|
|
| {
|
| # XXX Not sure whether the following tests are correct
|
| is(+'Inf ', Inf, "numification of strings with whitspace (1)");
|
| is(+'Inf o', 0, "numification of strings with whitspace (2)");
|
| is(+'NaN ', NaN, "numification of strings with whitspace (3)");
|
| is(+'NaN a', 0, "numification of strings with whitspace (4)");
|
| is(+"Inf\t", Inf, "numification of strings with whitspace (5)");
|
| is(+"Inf\to", 0, "numification of strings with whitspace (6)");
|
| is(+"NaN\t", NaN, "numification of strings with whitspace (7)");
|
| is(+"NaN\ta", 0, "numification of strings with whitspace (8)");
|
| is(+"Inf\n", Inf, "numification of strings with whitspace (9)");
|
| is(+"Inf\no", 0, "numification of strings with whitspace (10)");
|
| is(+"NaN\n", NaN, "numification of strings with whitspace (11)");
|
| is(+"NaN\na", 0, "numification of strings with whitspace (12)");
|
| is(+"Inf\n\t ", Inf, "numification of strings with whitspace (13)");
|
| is(+"Inf\n\t o", 0, "numification of strings with whitspace (14)");
|
| is(+"NaN\n\t ", NaN, "numification of strings with whitspace (15)");
|
| is(+"NaN\n\t a", 0, "numification of strings with whitspace (16)");
|
| is(+"3 ", 3, "numification of strings with whitspace (17)");
|
| }
|
|
|
| is(+'aInf', 0, "+'aInf' is 0");
|
| is(+'aInfo', 0, "+'aInfo' is 0");
|
| is(+'aNaN', 0, "+'aNaN' is 0");
|
| is(+'aNaNa', 0, "+'aNaNa' is 0");
|
|
|
| is( Inf, 'Inf', "'Inf' is Inf");
|
| is(-Inf, '-Inf', "'-Inf' is -Inf");
|
|
|
| is(+(~(+Inf)), Inf, "'+Inf' is Inf");
|
| is(+(~(-Inf)), -Inf, "'-Inf' is -Inf");
|
|
|
|
|
| # RT #62622
|
| #?rakudo skip '+"2" should return an Int'
|
| {
|
| my Int $x;
|
|
|
| lives_ok { $x = +'2' }, 'can assign +"2" to Int variable';
|
| isa_ok( $x, Int, 'assign +"2" to Int creates an Int' );
|
| is( $x, 2, 'assign +"2" to Int variable works' );
|
|
|
| lives_ok { $x = "4" - 3 }, 'lives: Int $x = "4" - 3';
|
| isa_ok( $x, Int, 'Int $x = "4" - 3 creates an Int' );
|
| is( $x, 1, 'works: Int $x = "4" - 3' );
|
| }
|
|
|
| # TimToady says this should work, see http://irclog.perlgeek.de/perl6/2009-11-29#i_1780142
|
| {
|
| my Num $x;
|
|
|
| lives_ok { $x = +'2' }, 'can assign string "2" to Num variable';
|
| isa_ok( $x, Num, 'assign string "2" to Num creates an Num' );
|
| is( $x, 2, 'assign string "2" to Num variable works' );
|
|
|
| lives_ok { $x = "4" - 3 }, 'lives: Num $x = "4" - 3';
|
| isa_ok( $x, Num, 'Num $x = "4" - 3 creates an Num' );
|
| is( $x, 1, 'works: Num $x = "4" - 3' );
|
| }
|
|
|
| # vim: ft=perl6
|
- Bitwise operators get a data type prefix:
+, ~, or ?. For example, Perl 5's | becomes either +| or ~| or ?|, depending on whether the operands are to be treated as numbers, strings, or boolean values. Perl 5's left shift << becomes +< , and correspondingly with right shift. Perl 5's unary ~ (one's complement) becomes either +^ or ~^ or ?^, since a bitwise NOT is like an exclusive-or against solid ones. Note that ?^ is functionally identical to !, but conceptually coerces to boolean first and then flips the bit. Please use ! instead. As explained in "Assignment operators", a bitwise operator can be turned into its corresponding assignment operator by following it with =. For example Perl 5's <<= becomes +<= .
From t/spec/S03-operators/bit.t lines 13–101: (skip)
-
| # L<S03/Changes to Perl 5 operators/Bitwise operators get a data type prefix>
|
| {
|
|
|
| # numeric
|
| is( 0xdead +& 0xbeef, 0x9ead, 'numeric bitwise +& of hexadecimal' );
|
| is( 0xdead +| 0xbeef, 0xfeef, 'numeric bitwise +| of hexadecimal' );
|
| is( 0xdead +^ 0xbeef, 0x6042, 'numeric bitwise +^ of hexadecimal' );
|
| is( +^0xdead +& 0xbeef, 0x2042, 'numeric bitwise +^ and +& together' );
|
|
|
| # string
|
| is( 'a' ~& 'A', 'A', 'string bitwise ~& of "a" and "A"' );
|
| is( 'a' ~| 'b', 'c', 'string bitwise ~| of "a" and "b"' );
|
| is( 'a' ~^ 'B', '#', 'string bitwise ~^ of "a" and "B"' );
|
| is( 'AAAAA' ~& 'zzzzz', '@@@@@', 'short string bitwise ~&' );
|
| is( 'AAAAA' ~| 'zzzzz', '{{{{{', 'short string bitwise ~|' );
|
| is( 'AAAAA' ~^ 'zzzzz', ';;;;;', 'short string bitwise ~^' );
|
|
|
| # long strings
|
| my $foo = "A" x 150;
|
| my $bar = "z" x 75;
|
| my $zap = "A" x 75;
|
|
|
| is( $foo ~& $bar, '@' x 75, 'long string bitwise ~&, truncates' );
|
| is( $foo ~| $bar, '{' x 75 ~ $zap, 'long string bitwise ~|, no truncation' );
|
| is( $foo ~^ $bar, ';' x 75 ~ $zap, 'long string bitwise ~^, no truncation' );
|
|
|
| # "interesting" tests from a long time back...
|
| #?rakudo 2 skip 'bitwise string manipulation'
|
| is( "ok \xFF\xFF\n" ~& "ok 19\n", "ok 19\n", 'stringwise ~&, arbitrary string' );
|
| is( "ok 20\n" ~| "ok \0\0\n", "ok 20\n", 'stringwise ~|, arbitrary string' );
|
|
|
| # bit shifting
|
| is( 32 +< 1, 64, 'shift one bit left' );
|
| is( 32 +> 1, 16, 'shift one bit right' );
|
| is( 257 +< 7, 32896, 'shift seven bits left' );
|
| is( 33023 +> 7, 257, 'shift seven bits right' );
|
|
|
| # Tests to see if you really can do casts negative floats to unsigned properly
|
| my $neg1 = -1.0.Num;
|
| my $neg7 = -7.0.Num;
|
|
|
| is(+^ $neg1, 0, 'cast numeric float to unsigned' );
|
| is(+^ $neg7, 6, 'cast -7 to 6 with +^' );
|
| ok(+^ $neg7 == 6, 'cast -7 with equality testing' );
|
|
|
| }
|
|
|
|
|
| # signed vs. unsigned
|
| #ok((+^0 +> 0 && do { use integer; ~0 } == -1));
|
|
|
| #my $bits = 0;
|
| #for (my $i = ~0; $i; $i >>= 1) { ++$bits; }
|
| #my $cusp = 1 << ($bits - 1);
|
|
|
|
|
| #ok(($cusp & -1) > 0 && do { use integer; $cusp & -1 } < 0);
|
| #ok(($cusp | 1) > 0 && do { use integer; $cusp | 1 } < 0);
|
| #ok(($cusp ^ 1) > 0 && do { use integer; $cusp ^ 1 } < 0);
|
| #ok((1 << ($bits - 1)) == $cusp &&
|
| # do { use integer; 1 << ($bits - 1) } == -$cusp);
|
| #ok(($cusp >> 1) == ($cusp / 2) &&
|
| # do { use integer; abs($cusp >> 1) } == ($cusp / 2));
|
|
|
| #--
|
| #$Aaz = chr(ord("A") & ord("z"));
|
| #$Aoz = chr(ord("A") | ord("z"));
|
| #$Axz = chr(ord("A") ^ ord("z"));
|
| # instead of $Aaz x 5, literal "@@@@@" is used and thus ascii assumed below
|
| # (for now...)
|
|
|
|
|
| # currently, pugs recognize octals as "\0o00", not "\o000".
|
| #if ("o\o000 \0" ~ "1\o000" ~^ "\o000k\02\o000\n" eq "ok 21\n") { say "ok 15" } else { say "not ok 15" }
|
|
|
| # Pugs does not have \x{}
|
|
|
| #
|
| #if ("ok \x{FF}\x{FF}\n" ~& "ok 22\n" eq "ok 22\n") { say "ok 16" } else { say "not ok 16" }
|
| #if ("ok 23\n" ~| "ok \x{0}\x{0}\n" eq "ok 23\n") { say "ok 17" } else { say "not ok 17" }
|
| #if ("o\x{0} \x{0}4\x{0}" ~^ "\x{0}k\x{0}2\x{0}\n" eq "ok 24\n") { say "ok 18" } else { say "not ok 18" }
|
|
|
| # Not in Pugs: vstrings, ebcdic, unicode, sprintf
|
|
|
| # More variations on 19 and 22
|
| #if ("ok \xFF\x{FF}\n" ~& "ok 41\n" eq "ok 41\n") { say "ok 19" } else { say "not ok 19" }
|
| #if ("ok \x{FF}\xFF\n" ~& "ok 42\n" eq "ok 42\n") { say "ok 20" } else { say "not ok 20" }
|
|
|
| # vim: ft=perl6
|
?| is a logical OR but differs from || in that ?| always evaluates both sides and returns a standard boolean value. That is, it's equivalent to ?$a + ?$b != 0. Another difference is that it has the precedence of an additive operator.
?& is a logical AND but differs from && in that ?& always evaluates both sides and returns a standard boolean value. That is, it's equivalent to ?$a * ?$b != 0. Another difference is that it has the precedence of a multiplicative operator.
Bitwise string operators (those starting with ~) may only be applied to buf types or similar compact integer arrays, and treat the entire chunk of memory as a single huge integer. They differ from the + operators in that the + operators would try to convert the string to a number first on the assumption that the string was an ASCII representation of a number.
x splits into two operators: x (which concatenates repetitions of a string to produce a single string), and xx (which creates a list of repetitions of a list or item). "foo" xx * represents an arbitrary number of copies, useful for initializing lists. The left side of an xx is evaluated only once. (To call a block repeatedly, use a map instead.)
From t/spec/S03-operators/repeat.t lines 13–21: (skip)
-
| #L<S03/Changes to Perl 5 operators/"x (which concatenates repetitions of a string to produce a single string">
|
|
|
| is('a' x 3, 'aaa', 'string repeat operator works on single character');
|
| is('ab' x 4, 'abababab', 'string repeat operator works on multiple character');
|
| is(1 x 5, '11111', 'number repeat operator works on number and creates string');
|
| is('' x 6, '', 'repeating an empty string creates an empty string');
|
| is('a' x 0, '', 'repeating zero times produces an empty string');
|
| is('a' x -1, '', 'repeating negative times produces an empty string');
|
|
|
From t/spec/S03-operators/repeat.t lines 22–78: (skip)
-
| #L<S03/Changes to Perl 5 operators/"and xx (which creates a list of repetitions of a list or item)">
|
| my @foo = 'x' xx 10;
|
| is(@foo[0], 'x', 'list repeat operator created correct array');
|
| is(@foo[9], 'x', 'list repeat operator created correct array');
|
| is(+@foo, 10, 'list repeat operator created array of the right size');
|
|
|
|
|
| lives_ok { my @foo2 = Mu xx 2; }, 'can repeat Mu';
|
| my @foo3 = (1, 2) xx 2;
|
| is(@foo3[0], 1, 'can repeat lists');
|
| is(@foo3[1], 2, 'can repeat lists');
|
| is(@foo3[2], 1, 'can repeat lists');
|
| is(@foo3[3], 2, 'can repeat lists');
|
|
|
| my @foo4 = 'x' xx 0;
|
| is(+@foo4, 0, 'repeating zero times produces an empty list');
|
|
|
| my @foo5 = 'x' xx -1;
|
| is(+@foo5, 0, 'repeating negative times produces an empty list');
|
|
|
| my @foo_2d = [1, 2] xx 2; # should create 2d
|
| #?pugs todo 'bug'
|
| is(@foo_2d[1], [1, 2], 'can create 2d arrays'); # creates a flat 1d array
|
| # Wrong/unsure: \(1, 2) does not create a ref to the array/list (1,2), but
|
| # returns a list containing two references, i.e. (\1, \2).
|
| #my @foo_2d2 = \(1, 2) xx 2; # just in case it's a parse bug
|
| ##?pugs todo 'bug'
|
| #is(@foo_2d[1], [1, 2], 'can create 2d arrays (2)'); # creates a flat 1d array
|
|
|
| # test x=
|
| my $twin = 'Lintilla';
|
| ok($twin x= 2, 'operator x= for string works');
|
| is($twin, 'LintillaLintilla', 'operator x= for string repeats correct');
|
|
|
| {
|
| my @array = (4, 2);
|
| ok(@array xx= 2, 'operator xx= for list works');
|
| is(@array[0], 4, 'operator xx= for list repeats correct');
|
| is(@array[3], 2, 'operator xx= for list repeats correct');
|
| is(+@array, 4, 'operator xx= for list created the right size');
|
| }
|
|
|
| # test that xx actually creates independent items
|
| {
|
| my @a = 'a' xx 3;
|
| is @a.join('|'), 'a|a|a', 'basic infix:<xx>';
|
| @a[0] = 'b';
|
| is @a.join('|'), 'b|a|a', 'change to one item left the others unchanged';
|
|
|
| my @b = <x y> xx 3;
|
| is @b.join('|'), 'x|y|x|y|x|y', 'basic sanity with <x y> xx 3';
|
| @b[0] = 'z';
|
| @b[3] = 'a';
|
| is @b.join('|'), 'z|y|x|a|x|y', 'change to one item left the others unchanged';
|
| }
|
|
|
| # vim: ft=perl6
|
- The
? : conditional operator becomes ?? !!. A pseudo operator, infix:<?>, catches migratory brainos at compile time.
From t/spec/S03-operators/ternary.t lines 8–76: (skip)
-
| #L<S03/Changes to Perl 5 operators/"The ? : conditional operator becomes ?? !!">
|
|
|
| my $str1 = "aaa";
|
| my $str2 = "aaa";
|
| my $str3 = "bbb";
|
|
|
| {
|
| my $foo = "";
|
| $str1 eq $str2 ?? ($foo = 1) !! ($foo = 2);
|
| is($foo, 1, "?? !!");
|
|
|
| $str1 eq $str3 ?? ($foo = 3) !! ($foo = 4);
|
| is($foo, 4, "?? !!");
|
| }
|
|
|
| is(($str2 eq $str1 ?? 8 * 8 !! 9 * 9), 64, "?? !! in parenthesis");
|
| is(($str2 eq $str3 ?? 8 + 8 !! 9 div 9), 1, "?? !! in parenthesis");
|
|
|
| is(1 ?? 2 ?? 3 !! 4 !! 5 ?? 6 !! 7, 3, "nested ?? !!");
|
| is(1 ?? 0 ?? 3 !! 4 !! 5 ?? 6 !! 7, 4, "nested ?? !!");
|
| is(0 ?? 2 ?? 3 !! 4 !! 5 ?? 6 !! 7, 6, "nested ?? !!");
|
| is(0 ?? 2 ?? 3 !! 4 !! 0 ?? 6 !! 7, 7, "nested ?? !!");
|
|
|
| {
|
| my @a = (1 ?? 2 !! 3, 4 ?? 5 !! 6);
|
| is(@a, [2, 5], "?? !! in array");
|
|
|
| }
|
|
|
| is((0 and 1 ?? 2 !! 3), 0, "operator priority");
|
| is((4 or 5 ?? 6 !! 7), 4, "operator priority");
|
|
|
| {
|
| my $foo = 8;
|
|
|
| $foo = 9 ?? 10 !! 11;
|
| is($foo, 10, "operator priority");
|
|
|
| $foo = 0 ?? 12 !! 13;
|
| is($foo, 13, "operator priority");
|
| }
|
|
|
| #?pugs skip "parse failure"
|
| {
|
| # This parses incorrectly in pugs because it's
|
| # parsed as Bool::True(!! Bool::False).
|
| my $foo = 1 ?? Bool::True !! Bool::False;
|
| is($foo, Bool::True, "a statement with both ??!! and :: in it did compile") ;
|
| }
|
|
|
| {
|
| # Defining an N! postfix (for factorial) causes a misparse on ternary op
|
| proto postfix:<!>($n) {
|
| return 1 if $n < 2;
|
| return $n * ($n-1)!
|
| }
|
|
|
| my $foo = eval q[ 1 ?? 'yay' !! 'nay' ];
|
| #?pugs todo 'bug'
|
| is($foo, "yay", "defining a postfix<!> doesn't screw up ternary op");
|
| }
|
|
|
| eval_dies_ok q[ 1 ?? 2,3 !! 4,5 ], 'Ternary error (RT 66840)';
|
|
|
| eval_dies_ok q[ 71704 !! 'bust' ], 'Ternary error (RT 71704)';
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
qw{ ... } gets a synonym: < ... >, and an interpolating variant, «...». For those still living without the blessings of Unicode, that can also be written: << ... >>.
- Comma
, now constructs a Parcel object from its operands. In item context this turns into a Seq object. You have to use a [*-1] subscript to get the last one. (Note the *. Negative subscripts no longer implicitly count from the end; in fact, the compiler may complain if you use [-1] on an object known at compile time not to have negative subscripts.)
- The unary backslash operator is not really an operator, but a special noun form. It "captures" its argument or arguments, and returns an object representing those arguments. You can dereference this object in several ways to retrieve different parts of the arguments; see the definition of
Capture in S02 for details. (No whitespace is allowed after the backslash because that would instead start an "unspace", that is, an escaped sequence of whitespace or comments. See S02 for details. However, oddly enough, because of that unspace rule, saying \\ $foo turns out to be equivalent to \$foo.)
- The old
.. flipflop operator is now done with ff operator. (.. now always produces a Range object even in item context.) The ff operator may take a caret on either end to exclude either the beginning or ending. There is also a corresponding fff operator with Perl 5's ... semantics. You may say
From t/spec/S03-operators/flip-flop.t lines 7–174: (skip)
-
| # L<S03/Changes to Perl 5 operators/flipflop operator is now done with>
|
|
|
| # XXX more tests for fff
|
|
|
| #?pugs 999 skip 'TODO: infix:<ff>'
|
| sub my_take (Int $n, &f) { (1..$n).map: { f() ?? $_ !! Nil } }
|
| sub always_false { 0 }
|
| sub always_true { 1 }
|
|
|
| # Basic ff
|
| {
|
| ok 1 ff 1, 'flip-flop operator implemented';
|
| ok 1 fff 1, 'fff operator implemented';
|
| }
|
|
|
| {
|
| my @result = my_take 5, { ?(always_false() ff always_false()) };
|
| is ~@result, " ", "always_false() ff always_false()";
|
| }
|
| {
|
| my @result = my_take 5, { ?(always_false() fff always_false()) };
|
| is ~@result, " ", "always_false() fff always_false()";
|
| }
|
|
|
| {
|
| my @result = my_take 5, { ?(always_false() ff always_true()) };
|
| is ~@result, " ", "always_false() ff always_true()";
|
| }
|
| {
|
| my @result = my_take 5, { ?(always_false() fff always_true()) };
|
| is ~@result, " ", "always_false() fff always_true()";
|
| }
|
|
|
| {
|
| my @result = my_take 5, { ?(always_true() ff always_true()) };
|
| ok all(@result), "always_true() ff always_true()";
|
| }
|
|
|
| {
|
| my @result = my_take 5, { ?(always_true() ff always_false()) };
|
| is ~@result, "1 2 3 4 5", "always_true() ff always_false()";
|
| }
|
|
|
| # Basic ^ff
|
| {
|
| my @result = my_take 5, { ?(always_false() ^ff always_false()) };
|
| is ~@result, " ", "always_false() ^ff always_false()";
|
| }
|
|
|
| {
|
| my @result = my_take 5, { ?(always_false() ^ff always_true()) };
|
| is ~@result, " ", "always_false() ^ff always_true()";
|
| }
|
|
|
| #?rakudo todo "Very strange test..."
|
| {
|
| my @result = my_take 5, { ?(always_true() ^ff always_true()) } || 1;
|
| diag(+@result ~ ' ' ~@result);
|
| my $first = shift @result;
|
|
|
| ok !$first && all(@result), "always_true() ^ff always_true()";
|
| }
|
|
|
| {
|
| my @result = my_take 5, { ?(always_true() ^ff always_false()) };
|
| is ~@result, " 2 3 4 5", "always_true() ^ff always_false()";
|
| }
|
|
|
| # Basic ff^
|
| {
|
| my @result = my_take 5, { ?(always_false() ff^ always_false()) };
|
| is ~@result, " ", "always_false() ff^ always_false()";
|
| }
|
|
|
| {
|
| my @result = my_take 5, { ?(always_false() ff^ always_true()) };
|
| is ~@result, " ", "always_false() ff^ always_true()";
|
| }
|
|
|
| {
|
| my @result = my_take 5, { ?(always_true() ff^ always_true()) };
|
|
|
| # XXX what should the result be?
|
| }
|
|
|
| {
|
| my @result = my_take 5, { ?(always_true() ff^ always_false()) };
|
| is ~@result, "1 2 3 4 5", "always_true() ff^ always_false()";
|
| }
|
|
|
| # RHS not evaluated when in "false" state (perldoc perlop, /flip-flop)
|
| {
|
| my $bug = 1;
|
| if 0 ff {$bug=2} { $bug = 3 };
|
| ok ($bug == 1), "RHS not evaluated in \"false\" state (ff)";
|
|
|
| $bug = 1;
|
| if 0 ^ff {$bug=2} { $bug = 3 };
|
| ok ($bug == 1), "RHS not evaluated in \"false\" state (^ff)";
|
|
|
| $bug = 1;
|
| if 0 ff^ {$bug=2} { $bug = 3 };
|
| ok ($bug == 1), "RHS not evaluated in \"false\" state (ff^)";
|
| }
|
|
|
| # LHS not evaluated when in "true" state (perldoc perlop, /flip-flop)
|
| {
|
| my $count = 0;
|
| for (1..2) -> {
|
| if sub { ++$count; } ff 0 { }
|
| }
|
| is($count, 1, "LHS not evaluated in \"true\" state (ff)");
|
|
|
| for (1..2) -> {
|
| if sub { ++$count; } ^ff 0 { }
|
| }
|
| is($count, 2, "LHS not evaluated in \"true\" state (^ff)");
|
|
|
| for (1..2) -> {
|
| if sub { ++$count; } ff^ 0 { }
|
| }
|
| is($count, 3, "LHS not evaluated in \"true\" state (ff^)");
|
| }
|
|
|
| # LHS always evaluated when in "false" state
|
| #TODO Add same tests for RHS
|
| {
|
| my $count = 0;
|
| my $got = 0;
|
|
|
| for (1..2) -> {
|
| if sub { $count++ == 1 } ff 0 { ++$got }
|
| }
|
| is($count, 2, "LHS evaluated in \"false\" state (ff)");
|
| is($got, 1, ".. and result used");
|
| }
|
|
|
| {
|
| my $count = 0;
|
| my $got = 0;
|
| for (1..3) -> {
|
| if sub { $count++ > 0 } ^ff 0 { ++$got }
|
| }
|
| is($count, 2, "LHS evaluated only in \"false\" state (^ff)");
|
| is($got, 1, ".. and result used");
|
| }
|
|
|
| {
|
| my $count = 0;
|
| my $got = 0;
|
|
|
| for (1..2) -> {
|
| if sub { $count++ == 1 } ff^ 0 { ++$got }
|
| }
|
| is($count, 2, "LHS evaluated in \"false\" state (ff^)");
|
| is($got, 1, ".. and result used");
|
| }
|
|
|
|
|
| # See thread "till (the flipflop operator, formerly ..)" on p6l started by Ingo
|
| # Blechschmidt, especially Larry's reply:
|
| # http://www.nntp.perl.org/group/perl.perl6.language/24098
|
| {
|
| ok do { my sub foo ($x) { try { $x ff 0 } }; if foo(0) || !foo(1) || !foo(0) { die }},
|
| "all sub invocations share the same ff-state";
|
| }
|
|
|
| # vim: ft=perl6
|
/foo/ ff *
to indicate a flipflop that never flops once flipped.
- All comparison operators are unified at the same precedence level. See "Chained comparisons" below.
- The list assignment operator now parses on the right like any other list operator, so you don't need parens on the right side of:
From t/spec/S03-operators/assign.t lines 7–698: (skip)
-
| # L<S03/Changes to Perl 5 operators/list assignment operator now parses on the right>
|
|
|
| plan 240;
|
|
|
|
|
| # tests various assignment styles
|
| {
|
| my ($foo, $bar) = ("FOO", "BAR");
|
| is($foo, "FOO", "assigned correct value to first of two scalars");
|
| is($bar, "BAR", "... and second");
|
|
|
| ($foo, $bar) = ($bar, $foo);
|
| is($foo, "BAR", "swap assignment works for the first value");
|
| is($bar, "FOO", "... and second");
|
| }
|
|
|
| {
|
| my $x = 1;
|
| &infix:<=>.($x, 0);
|
| is($x, 0, 'assignment operator called as function');
|
| my Int $y;
|
| lives_ok { &infix:<=>($y, 3) }, 'assignment as function with types (1)';
|
| dies_ok { &infix:<=>($y, 'foo') }, 'assignment as function with types (2)';
|
|
|
| }
|
|
|
| {
|
| my $x = 1;
|
| infix:<=>($x, 0);
|
| is($x, 0, 'assignment operator called as function');
|
| }
|
|
|
| #?rakudo skip 'assigning to array slices'
|
| {
|
| # swap two elements in the same array
|
| # (moved this from array.t)
|
| my @a = 1 .. 5;
|
| @a[0,1] = @a[1,0];
|
| is(@a[0], 2, "slice assignment swapping two element in the same array");
|
| is(@a[1], 1, "slice assignment swapping two element in the same array");
|
| }
|
|
|
| #?rakudo skip 'assigning to array slices'
|
| {
|
| # swap two elements as slice, dwim slice from @b subscript
|
|
|
| my @a = 1 .. 2;
|
| my @b = 0 .. 2;
|
| @a[@b] = @a[1, 0], 3;
|
| is(@a[0], 2, "slice assignment swapping with array dwim");
|
| is(@a[1], 1, "slice assignment swapping with array dwim");
|
| is(@a[2], 3, "slice assignment swapping with array dwim makes listop");
|
| }
|
|
|
| {
|
| # list assignments
|
|
|
| my @a = 1 .. 3;
|
| my ($one, $two, $three) = @a;
|
| is($one, 1, 'list assignment my ($, $, $) = @ works');
|
| is($two, 2, 'list assignment my ($, $, $) = @ works');
|
| is($three, 3, 'list assignment my ($, $, $) = @ works');
|
|
|
| }
|
|
|
|
|
| #?pugs skip 'skipping assignment with skipped values via $'
|
| {
|
| # testing list assignments with skipped values
|
| my ($one, $, $three) = 1..3;
|
| is("$one $three", "1 3", 'list assignment my ($a, $, $b) = @ works');
|
|
|
| my ($, $two) = 1..2;
|
| is($two, 2, 'list assignment my ($, $a) = @ works');
|
| my ($, $, $, $four) = 1..4;
|
| is($four, 4, 'list assignment my ($, $, $, $a) = @ works');
|
|
|
| my ($, @b, $c) = 1..4;
|
| is(~@b, "2 3 4", 'list assignment my ($, @) = @ works');
|
| ok(!defined($c), 'list assignment my ($, @, $c) = @ works');
|
| }
|
|
|
| #?pugs skip "skipping assignment with skipped values via * in signature"
|
| {
|
| # testing list assignments with skipped values
|
| my ($one, $, $three) = 1..3;
|
| is("$one $three", "1 3", 'list assignment my ($a, $, $b) = @ works');
|
|
|
| my ($, $two) = 1..2;
|
| is($two, 2, 'list assignment my ($, $a) = @ works');
|
| my ($, $, $, $four) = 1..4;
|
| is($four, 4, 'list assignment my ($, $, $, $a) = @ works');
|
|
|
| my ($, @b, $c) = 1..4;
|
| is(~@b, "2 3 4", 'list assignment my ($, @) = @ works');
|
| ok(!defined($c), 'list assignment my ($, @, $c) = @ works');
|
| }
|
|
|
| #?pugs skip "skipping assignment with skipped values via * in lvalue"
|
| {
|
| # testing list assignments with skipped values
|
| my ($one, $two, $three, $four);
|
| ($one, *, $three) = 1..3;
|
| is("$one $three", "1 3", 'list assignment ($a, *, $b) = @ works');
|
|
|
| (*, $two, *) = 1..3;
|
| is($two, 2, 'list assignment (*, $a, *) = @ works');
|
| (*, *, *, $four) = 1..4;
|
| is($four, 4, 'list assignment (*, *, *, $a) = @ works');
|
|
|
| my (@b, $c);
|
| (*, @b, $c) = 1..4;
|
| is(~@b, "2 3 4", 'list assignment (*, @) = @ works');
|
| ok(!defined($c), 'list assignment (*, @, $c) = @ works');
|
| }
|
|
|
| #?rakudo skip 'assigning to *@'
|
| {
|
| # testing signature binding with skipped values via *@ in a signature
|
| my ($one, *@) = 1..4;
|
| is($one, 1, 'signature binding my ($one, *@) = 1..4 works');
|
| my ($a, $b, *@) = 1..4;
|
| is("$a $b", "1 2", 'signature binding my ($a, $b, *@) = 1..4 works');
|
| my ($c, $d, *@) = 1..2;
|
| is("$c $d", "1 2", 'signature binding my ($c, $d, *@) = 1..2 works');
|
| }
|
|
|
| {
|
| # testing list assignment syntax
|
|
|
| my ($a,$b,$c,@a);
|
| ($a,$b,$c) = 1 .. 3;
|
| @a = 1 .. 3;
|
| my ($s,@b) = 1 .. 3;
|
|
|
| is($a,1,"'\$a' is '1'?: (\$,\$,\$) = 1 .. 3");
|
| is($b,2,"'\$b' is '2'?: (\$,\$,\$) = 1 .. 3");
|
| is($c,3,"'\$c' is '3'?: (\$,\$,\$) = 1 .. 3");
|
| is(@a,'1 2 3',"'{\@a}' is '1 2 3'?: \@a = 1 .. 3");
|
| is($s,'1', "\$s is '1'?: my (\$s,\@a) = 1 .. 3");
|
| is(@b,'2 3',"'{\@b}' is '2 3'?: my (\$s,\@a) = 1 .. 3");
|
| }
|
|
|
| #?rakudo skip 'assigning to array slices'
|
| {
|
| my @a;
|
| @a[1, 2, 3] = 100, 200, 300;
|
| is(@a[1], 100, "assigned correct value from list to sliced array");
|
| is(@a[2], 200, "... and second");
|
| is(@a[3], 300, "... and third");
|
| ok(!defined(@a[0]), "won't modify unassigned one");
|
|
|
| my @b;
|
| (@b[2, 1, 0]) = 401, 201, 1;
|
| is(@b[0], 1, "assigned correct value from list to unsorted sliced array");
|
| is(@b[1], 201, "... and second");
|
| is(@b[2], 401, "... and third");
|
| }
|
|
|
| #?rakudo skip 'assigning to array slices'
|
| {
|
| my @c;
|
| my @d;
|
| (@c[1, 2], @c[3], @d) = 100, 200, 300, 400, 500;
|
| is(@c[1], 100, "assigned correct value from list to slice-in-list");
|
| is(@c[2], 200, "... and second");
|
| #?pugs 3 todo 'feature'
|
| is(@c[3], 300, "... and third");
|
| is(@d[0], 400, "... and fourth");
|
| is(@d[1], 500, "... and fifth");
|
| ok(!defined(@c[0]), "won't modify unassigned one");
|
|
|
| }
|
|
|
| {
|
| # chained @array = %hash = list assignment
|
| my (@a, @b, %h);
|
| @a = %h = 1,2;
|
| @b = %h;
|
| is(@a[0], @b[0], 'chained @ = % = list assignment');
|
| is(@a[1], @b[1], 'chained @ = % = list assignment');
|
| }
|
|
|
| #?rakudo skip 'Odd number of elements found where hash expected'
|
| {
|
| # chained $scalar = %hash = list assignment
|
| my ($s, $t, %h);
|
| $s = %h = 1,2;
|
| $t = %h;
|
| is($s, $t, 'chained $ = % = list assignment');
|
| }
|
|
|
| #?rakudo todo '(@a, @b) = (@b, @a)'
|
| {
|
| # (@b, @a) = (@a, @b) assignment
|
| my (@a, @b);
|
| @a = 1;
|
| @b = 2;
|
| (@b, @a) = (@a, @b);
|
| ok(!defined(@a[0]), '(@b, @a) = (@a, @b) assignment \@a[0] == undefined');
|
| is(@b[0], 1, '(@b, @a) = (@a, @b) assignment \@b[0]');
|
| is(@b[1], 2, '(@b, @a) = (@a, @b) assignment \@b[1]');
|
| }
|
|
|
| #?rakudo todo '(@a, @b) = (@b, @a)'
|
| {
|
| # (@b, @a) = @a, @b assignment
|
| my (@a, @b);
|
| @a = (1);
|
| @b = (2);
|
| (@b, @a) = @a, @b;
|
| ok(!defined(@a[0]), '(@b, @a) = @a, @b assignment \@a[0] == undefined');
|
| is(@b[0], 1, '(@b, @a) = @a, @b assignment \@b[0]');
|
| is(@b[1], 2, '(@b, @a) = @a, @b assignment \@b[1]');
|
| }
|
|
|
| my @p;
|
|
|
| #?rakudo skip '@p = $a ||= 3, 4'
|
| {
|
| my $a;
|
| @p = $a ||= 3, 4;
|
| is($a,3, "||= operator");
|
| is(@p[0],3, "||= operator parses as item assignment 1");
|
| is(@p[1],4, "||= operator parses as item assignment 2");
|
| @p = $a ||= 10, 11;
|
| is($a,3, "... and second");
|
| is(@p[0],3, "||= operator parses as item assignment 3");
|
| is(@p[1],11, "||= operator parses as item assignment 4");
|
| }
|
|
|
| {
|
| my $a;
|
| @p = $a //= 3, 4;
|
| is($a, 3, "//= operator");
|
| is(@p[0],3, "//= operator parses as item assignment 1");
|
| is(@p[1],4, "//= operator parses as item assignment 2");
|
| @p = $a //= 10, 11;
|
| is($a, 3, "... and second");
|
| is(@p[0],3, "//= operator parses as item assignment 3");
|
| is(@p[1],11, "//= operator parses as item assignment 4");
|
| my %hash;
|
| %hash<foo> //= hash();
|
| #?rakudo todo 'autoviv'
|
| isa_ok(%hash<foo>, Hash, "Verify //= autovivifies correctly");
|
| %hash<bar> //= [];
|
| isa_ok(%hash<bar>, Array, "Verify //= autovivifies correctly");
|
|
|
| my $f //= 5;
|
| is $f, 5, '//= also works in declaration';
|
| }
|
|
|
| #?rakudo skip '&&= in assignment'
|
| {
|
| my $a = 3;
|
| @p = $a &&= 42, 43;
|
| is($a, 42, "&&= operator");
|
| is(@p[0],42, "&&= operator parses as item assignment 1");
|
| is(@p[1],43, "&&= operator parses as item assignment 2");
|
| $a = 0;
|
| @p = $a &&= 10, 11;
|
| is($a, 0, "... and second");
|
| is(@p[0],0, "&&= operator parses as item assignment 3");
|
| is(@p[1],11, "&&= operator parses as item assignment 4");
|
| my $x = True; $x &&= False;
|
| is($x, False, "&&= operator with True and False");
|
| }
|
|
|
| {
|
| my $c;
|
| (($c = 3) = 4);
|
| is($c, 4, '(($c = 3) = 4) return val should be good as an lval');
|
| }
|
|
|
| {
|
| my $x = 42;
|
| @p = $x += 6, 7;
|
| is($x, 48, '+= operator');
|
| is(@p[0],48, "+= operator parses as item assignment 1");
|
| #?rakudo todo "item assignment parsing"
|
| is(@p[1],47, "+= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = 42;
|
| @p = $x -= 6, 7;
|
| is($x, 36, '-= operator');
|
| is(@p[0],36, "-= operator parses as item assignment 1");
|
| is(@p[1],7, "-= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = 4;
|
| @p = $x *= 3, 2;
|
| is($x, 12, '*= operator');
|
| is(@p[0],12, "*= operator parses as item assignment 1");
|
| #?rakudo todo 'item assignment'
|
| is(@p[1],12, "*= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = 6;
|
| @p = $x div= 3, 4;
|
| is($x, 2, 'div= operator');
|
| is(@p[0],2, "div= operator parses as item assignment 1");
|
| is(@p[1],4, "div= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = 2;
|
| @p = $x **= 3, 4;
|
| is($x, 8, '**= operator');
|
| is(@p[0],8, "**= operator parses as item assignment 1");
|
| is(@p[1],4, "**= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = "abc";
|
| @p = $x ~= "yz", "plugh";
|
| is($x, 'abcyz', '~= operator');
|
| is(@p[0],'abcyz', "~= operator parses as item assignment 1");
|
| is(@p[1],'plugh', "~= operator parses as item assignment 2");
|
| }
|
|
|
| # RT #64818
|
| {
|
| my $parsed = 0;
|
| eval_dies_ok q{$parsed = 1; my $foo = 'foo'; $foo R~= 'foo';},
|
| 'use of R~= operator dies';
|
| nok $parsed, 'use of R~= is a parse error';
|
| }
|
|
|
| {
|
| my $x = "abc";
|
| @p = $x x= 3, 4;
|
| is($x, 'abcabcabc', 'x= operator');
|
| is(@p[0],'abcabcabc', "x= operator parses as item assignment 1");
|
| is(@p[1],4, "x= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my @x = ( 'a', 'z' );
|
| @p = @x xx= 3, 4;
|
| is(+@x, 6, 'xx= operator elems');
|
| is(@x[0], 'a', 'xx= operator 0');
|
| is(@x[1], 'z', 'xx= operator 1');
|
| is(@x[2], 'a', 'xx= operator 2');
|
| is(@x[3], 'z', 'xx= operator 3');
|
| is(@x[4], 'a', 'xx= operator 4');
|
| is(@x[5], 'z', 'xx= operator 5');
|
| ok(!defined(@x[6]), 'xx= operator 6');
|
| is(~@p,~(@x,4), "xx= operator parses as item assignment 1");
|
| }
|
|
|
| {
|
| my $x = 1;
|
| @p = $x +&= 2, 3;
|
| is($x, 0, '+&= operator');
|
| is(@p[0],0, "+&= operator parses as item assignment 1");
|
| is(@p[1],3, "+&= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = 1;
|
| @p = $x +|= 2, 123;
|
| is($x, 3, '+|= operator');
|
| is(@p[0],3, "+|= operator parses as item assignment 1");
|
| is(@p[1],123, "+|= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = "z";
|
| @p = $x ~&= "I", "J";
|
| is($x, 'H', '~&= operator');
|
| is(@p[0],'H', "~&= operator parses as item assignment 1");
|
| is(@p[1],'J', "~&= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = "z";
|
| @p = $x ~|= "I", "J";
|
| is($x, '{', '~|= operator');
|
| is(@p[0],'{', "~|= operator parses as item assignment 1");
|
| is(@p[1],'J', "~|= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = 4;
|
| @p = $x %= 3, 4;
|
| is($x, 1, '%= operator');
|
| is(@p[0],1, "%= operator parses as item assignment 1");
|
| is(@p[1],4, "%= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = 1;
|
| @p = $x +^= 3, 4;
|
| is($x, 2, '+^= operator');
|
| is(@p[0],2, "+^= operator parses as item assignment 1");
|
| is(@p[1],4, "+^= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = "z";
|
| @p = $x ~^= "C", "D";
|
| is($x, 9, '~^= operator');
|
| is(@p[0],9, "~^= operator parses as item assignment 1");
|
| is(@p[1],'D', "~^= operator parses as item assignment 2");
|
| }
|
|
|
| #?rakudo skip "unknown reasons"
|
| {
|
| my $x = 0;
|
| @p = $x ^^= 42, 43;
|
| is($x, 42, '^^= operator');
|
| is(@p[0],42, "^^= operator parses as item assignment 1");
|
| is(@p[1],43, "^^= operator parses as item assignment 2");
|
| }
|
|
|
| {
|
| my $x = 42;
|
| @p = $x ?|= 24, 25;
|
| is($x, 1, '?|= operator');
|
| is(@p[0],1, "?|= operator parses as item assignment 1");
|
| is(@p[1],25, "?|= operator parses as item assignment 2");
|
| }
|
|
|
| #?pugs eval 'parsefail'
|
| {
|
| my $x = 42;
|
| @p = $x ?&= 24, 25;
|
| is($x, 1, '?&= operator');
|
| is(@p[0],1, "?&= operator parses as item assignment 1");
|
| is(@p[1],25, "?&= operator parses as item assignment 2");
|
| }
|
|
|
| #?pugs eval 'parsefail'
|
| {
|
| my $x = 0;
|
| @p = $x ?^= 42, 43;
|
| is($x, 1, '?^= operator');
|
| is(@p[0],1, "?^= operator parses as item assignment 1");
|
| is(@p[1],43, "?^= operator parses as item assignment 2");
|
| }
|
|
|
| #?pugs eval 'parsefail'
|
| {
|
| my $x = 1;
|
| @p = $x +<= 8, 9;
|
| is($x, 256, '+<= operator');
|
| is(@p[0],256, "+<= operator parses as item assignment 1");
|
| is(@p[1],9, "+<= operator parses as item assignment 2");
|
| }
|
|
|
| #?pugs eval 'parsefail'
|
| {
|
| my $x = 511;
|
| @p = $x +>= 8, 9;
|
| is($x, 1, '+>= operator');
|
| is(@p[0],1, "+>= operator parses as item assignment 1");
|
| is(@p[1],9, "+>= operator parses as item assignment 2");
|
| }
|
|
|
| # XXX: The following tests assume autoconvertion between "a" and buf8 type
|
| #?pugs eval 'parsefail'
|
| #?rakudo skip "unknown reasons"
|
| {
|
| my $x = "a";
|
| @p = $x ~<= 8, 9;
|
| is($x, "a\0", '~<= operator');
|
| is(@p[0],"a\0", "~<= operator parses as item assignment 1");
|
| is(@p[1],9, "~<= operator parses as item assignment 2");
|
| }
|
|
|
| #?pugs eval 'parsefail'
|
| #?rakudo skip "unknown reasons"
|
| {
|
| my $x = "aa";
|
| @p = $x ~>= 8, 9;
|
| is($x, "a", '~>= operator');
|
| is(@p[0],"a", "~>= operator parses as item assignment 1");
|
| is(@p[1],9, "~>= operator parses as item assignment 2");
|
| }
|
|
|
| # Tests of dwimming scalar/listiness of lhs
|
|
|
| sub l () { 1, 2 };
|
|
|
| {
|
| my $x;
|
| $x = l(), 3, 4;
|
| is $x.elems, 2, 'item assignment infix:<=> is tighter than the comma';
|
| }
|
|
|
| {
|
| my $x;
|
| my @a = ($x = l(), 3, 4);
|
| is $x.elems, 2, 'item assignment infix:<=> is tighter than the comma (2)';
|
| is @a.elems, 3, 'item assignment infix:<=> is tighter than the comma (3)';
|
| }
|
|
|
| #?rakudo skip 'item assignment, $::(...)'
|
| {
|
| package Foo {
|
| our $b;
|
| my @z = ($::('Foo::b') = l(), l());
|
| is($b.elems, 2, q/lhs treats $::('Foo::b') as scalar (1)/);
|
| is(@z.elems, 3, q/lhs treats $::('Foo::b') as scalar (2)/);
|
| }
|
| }
|
|
|
| #?rakudo skip 'Autovivify full qualified name'
|
| {
|
| my @z = ($Foo::c = l, l);
|
| is($Foo::c.elems, 2, 'lhs treats $Foo::c as scalar (1)');
|
| is(@z.elems, 3, 'lhs treats $Foo::c as scalar (2)');
|
| }
|
|
|
| {
|
| my @a;
|
| my @z = ($(@a[0]) = l, l);
|
| is(@a[0].elems, 2, 'lhs treats $(@a[0]) as scalar (1)');
|
| #?rakudo todo 'item assignment'
|
| is(@z.elems, 2, 'lhs treats $(@a[0]) as scalar (2)');
|
| }
|
|
|
| {
|
| my $a;
|
| my @z = (($a) = l, l, l);
|
| is($a.elems, 6, 'lhs treats ($a) as list');
|
| #?rakudo todo 'item/list assignment'
|
| is(@z.elems, 6, 'lhs treats ($a) as list');
|
| }
|
|
|
| #?rakudo skip "Method 'elems' not found for invocant of class 'Integer'"
|
| {
|
| my $a;
|
| my @z = (($a, *) = l, l, l);
|
| is($a.elems, 1, 'lhs treats ($a, *) as list (1)');
|
| #?rakudo todo 'list assignment with ($var, *)'
|
| is(@z.elems, 6, 'lhs treats ($a, *) as list (2)');
|
| }
|
|
|
| #?rakudo skip '@$a'
|
| {
|
| my $a;
|
| my @z = (@$a = l, l, l);
|
| is($a.elems, 6, 'lhs treats @$a as list (1)');
|
| is @z.elems, 6, 'lhs treats @$a as list (2)';
|
| }
|
|
|
| #?rakudo skip '$a[] autovivification (unspecced?)'
|
| {
|
| my $a;
|
| $a[] = l, l, l
|
| is($a.elems, 6, 'lhs treats $a[] as list');
|
| }
|
|
|
| {
|
| my $a;
|
| my $b;
|
| my @z = (($a,$b) = l, l);
|
| is($a, 1, 'lhs treats ($a,$b) as list');
|
| is($b, 2, 'lhs treats ($a,$b) as list');
|
| is(+@z, 2, 'lhs treats ($a,$b) as list, and passes only two items on');
|
| }
|
|
|
| {
|
| my @a;
|
| my @z = (@a[0] = l, l);
|
| #?rakudo todo 'list assignment to scalar'
|
| is(@a[0].elems, 1, 'lhs treats @a[0] as one-item list');
|
| is(@z.elems, 1, 'lhs treats @a[0] as one-item list');
|
| ok(!defined(@a[1]), 'lhs treats @a[0] as one-item list');
|
| }
|
|
|
| {
|
| my @a;
|
| my @z = (@a[0,] = l, l);
|
| is(@a[0,].elems, 1, 'lhs treats @a[0,] as one-item list');
|
| is(@z.elems, 1, 'lhs treats @a[0,] as one-item list');
|
| ok(defined(@a[1,]), 'lhs treats @a[0,] as one-item list');
|
| }
|
|
|
| {
|
| my %a;
|
| my @z = (%a<x> = l, l);
|
| #?rakudo 2 todo 'list assignment to scalar'
|
| is(%a{"x"}.elems, 1, 'lhs treats %a<x> as one-item list');
|
| is(@z[0].elems, 1, 'lhs treats %a<x> as one-item list');
|
| ok(!defined(@z[1]), 'lhs treats %a<x> as one-item list');
|
| }
|
|
|
| {
|
| my %a;
|
| my @z = (%a<x y z> = l, l);
|
| is(%a<x>, 1, 'lhs treats %a<x y z> as list');
|
| is(%a<y>, 2, 'lhs treats %a<x y z> as list');
|
| is(%a<z>, 1, 'lhs treats %a<x y z> as list');
|
| }
|
|
|
| {
|
| my %a;
|
| my @z = (%a{'x'} = l, l);
|
| #?rakudo 2 todo 'list assignment to scalar'
|
| is(%a{"x"}, 1, q/lhs treats %a{'x'} as list/);
|
| is(~@z[0], '1', q/lhs treats %a{'x'} as list/);
|
| ok(!defined(@z[1]), q/lhs treats %a{'x'} as list/);
|
| }
|
|
|
| {
|
| my %a;
|
| my @z = (%a{'x','y','z'} = l, l);
|
| is(%a<x>, 1, q/lhs treats %a{'x','y','z'} as list/);
|
| is(%a<y>, 2, q/lhs treats %a{'x','y','z'} as list/);
|
| is(%a<z>, 1, q/lhs treats %a{'x','y','z'} as list/);
|
| is(@z[0], 1, q/lhs treats %a{'x','y','z'} as list/);
|
| is(@z[1], 2, q/lhs treats %a{'x','y','z'} as list/);
|
| is(@z[2], 1, q/lhs treats %a{'x','y','z'} as list/);
|
| }
|
|
|
| {
|
| my %a;
|
| my @z = (%a{'x'..'z'} = l, l);
|
| is(%a<x>, 1, q/lhs treats %a{'x'..'z'} as list/);
|
| is(%a<y>, 2, q/lhs treats %a{'x'..'z'} as list/);
|
| is(%a<z>, 1, q/lhs treats %a{'x'..'z'} as list/);
|
| is(@z[0], 1, q/lhs treats %a{'x'..'z'} as list/);
|
| is(@z[1], 2, q/lhs treats %a{'x'..'z'} as list/);
|
| is(@z[2], 1, q/lhs treats %a{'x'..'z'} as list/);
|
| }
|
|
|
|
|
| {
|
| my @a;
|
| my @b = (0,0);
|
| my $c = 1;
|
| my @z = (@a[@b[$c]] = l, l);
|
| #?rakudo 3 todo 'list assignment, autovivification (?)'
|
| is(~@a, '1', 'lhs treats @a[@b[$c]] as list');
|
| is(~@z[0], '1', 'lhs treats @a[@b[$c]] as list');
|
| is(!defined(@z[1]), 'lhs treats @a[@b[$c]] as list');
|
| }
|
|
|
| {
|
| my @a;
|
| my @b = (0,0);
|
| my $c = 1;
|
| my @z = (@a[@b[$c,]] = l, l);
|
| #?rakudo 2 todo 'list assignment'
|
| is(~@a, '1', 'lhs treats @a[@b[$c,]] as list');
|
| is(~@z[0], '2', 'lhs treats @a[@b[$c,]] as list');
|
| ok(!defined(@z[1]), 'lhs treats @a[@b[$c,]] as list');
|
| }
|
|
|
| #?rakudo skip 'unknown'
|
| {
|
| my @a;
|
| my $b = 0;
|
| my sub foo { \@a }
|
| my @z = (foo()[$b] = l, l);
|
| is(@a.elems, 1, 'lhs treats foo()[$b] as list');
|
| #?rakudo todo 'list assignment'
|
| is(@z[0].elems, 1, 'lhs treats foo()[$b] as list');
|
| ok(!defined(@z[1]), 'lhs treats foo()[$b] as list');
|
| }
|
|
|
| #?rakudo skip 'unknown'
|
| {
|
| my @a;
|
| my $b = 0;
|
| my sub foo { \@a }
|
| my @z = (foo()[$b,] = l, l);
|
| is(@a.elems, 1, 'lhs treats foo()[$b,] as list');
|
| #?rakudo todo 'list assignment'
|
| is(@z[0].elems, 1, 'lhs treats foo()[$b,] as list');
|
| ok(!defined(@z[1]), 'lhs treats foo()[$b,] as list');
|
| }
|
|
|
| #?rakudo skip 'unknown'
|
| {
|
| my @a;
|
| my $b = 0;
|
| my @z = ($(@a[$b]) = l, l);
|
| is(@a.elems, 1, 'lhs treats $(@a[$b]) as item (1)');
|
| #?rakudo 2 todo 'item assignment'
|
| is(@a[0].elems, 1, 'lhs treats $(@a[$b]) as item (2)');
|
| is(@z[1].elems, 3, 'lhs treats $(@a[$b]) as item (3)');
|
| }
|
|
|
|
|
|
|
From t/spec/S03-operators/assign-is-not-binding.t lines 9–49: (skip)
-
| # L<S03/Changes to Perl 5 operators/list assignment operator now parses on the right>
|
|
|
|
|
| # very simple assignment
|
|
|
| {
|
| my $foo = 'FOO';
|
| is $foo, 'FOO', 'Can assign string to scalar 1';
|
| my $bar = 'BAR';
|
| is $foo, 'FOO', 'Can assign string to scalar 2';
|
|
|
| $foo = $bar;
|
| is $foo, 'BAR', 'Can assign scalar to scalar';
|
|
|
| $foo = 'FOO';
|
| is $bar, 'BAR', "Assignment didn't create a binding";
|
| }
|
|
|
| # test that assignment from arrays to scalars doesn't create a binding:
|
|
|
| {
|
| my @array = 23, 42;
|
| is @array[0], 23, 'Could assign first element';
|
| is @array[1], 42, 'Could assign second element';
|
| my $temp = @array[0];
|
| is $temp, 23, 'Could retrieve first element to a scalar';
|
| #rakudo skip 'Broken in ng1'
|
| # {
|
| # @array[0] = @array[1];
|
| # is $temp, 23, "Assignment to scalar didn't create a binding"
|
| # }
|
| }
|
|
|
| {
|
| my $a = 42;
|
| my @list = ($a);
|
| $a = 24;
|
| is @list[0], 42, "Assignment to scalar didn't create a binding";
|
| }
|
|
|
| # vim: ft=perl6
|
@foo = 1, 2, 3;
You do still need them on the left for
($a, $b, $c) = 1, 2, 3;
since assignment operators are tighter than comma to their left.
"Don't care" positions may be indicated by assigment to the * token. A final * throws away the rest of the list:
($a, *, $c) = 1, 2, 3; # throw away the 2
($a, $b, $c, *) = 1..42; # throw away 4..42
(Within signature syntax, a bare $ can ignore a single argument as well, and a bare *@ can ignore the remaining arguments.)
List assignment offers the list on the right to each container on the left in turn, and each container may take one or more elements from the front of the list. If there are any elements left over, a warning is issued unless the list on the left ends with * or the final iterator on the right is defined in terms of *. Hence none of these warn:
($a, $b, $c, *) = 1..9999999;
($a, $b, $c) = 1..*;
($a, $b, $c) = 1 xx *;
($a, $b, $c) = 1, 2, *;
This, however, warns you of information loss:
($a, $b, $c) = 1, 2, 3, 4;
As in Perl 5, assignment to an array or hash slurps up all the remaining values, and can never produce such a warning. (It will, however, leave any subsequent lvalue containers with no elements, just as in Perl 5.)
The left side is evaluated completely for its sequence of containers before any assignment is done. Therefore this:
my $a = 0; my @b;
($a, @b[$a]) = 1, 2;
assigns 2 to @b[0], not @b[1].
- The item assignment operator expects a single expression with precedence tighter than comma, so
loop ($a = 1, $b = 2; ; $a++, $b++) {...}
works as a C programmer would expect. The term on the right of the = is always evaluated in item context.
The syntactic distinction between item and list assignment is similar to the way Perl 5 defines it, but has to be a little different because we can no longer decide the nature of an inner subscript on the basis of the outer sigil. So instead, item assignment is restricted to lvalues that are simple scalar variables, and assignment to anything else is parsed as list assignment. The following forms are parsed as "simple lvalues", and imply item assignment to the scalar container:
$a = 1 # scalar variable
$foo::bar = 1 # scalar package variable
$(ANY) = 1 # scalar dereference (including $$a)
$::(ANY) = 1 # symbolic scalar dereference
$foo::(ANY) = 1 # symbolic scalar dereference
Such a scalar variable lvalue may be decorated with declarators, types, and traits, so these are also item assignments:
my $fido = 1
my Dog $fido = 1
my Dog $fido is trained is vicious = 1
However, anything more complicated than that (including parentheses and subscripted expressions) forces parsing as list assignment instead. Assignment to anything that is not a simple scalar container also forces parsing as list assignment. List assignment expects an expression that is looser than comma precedence. The right side is always evaluated in list context:
($x) = 1,2,3
$x[1] = 1,2,3
@$array = 1,2,3
my ($x, $y) = 1,2,3
our %map = :a<1>, :b<2>
The rules of list assignment apply, so all the assignments involving $x above produce warnings for discarded values. A warning may be issued at compile time if it is detected that a run-time warning is inevitable.
The = in a default declaration within a signature is not really assignment, and is always parsed as item assignment. (That is, to assign a list as the default value you must use parentheses to hide any commas in the list value.)
To assign a list to a scalar value, you cannot say:
$a = 1, 2, 3;
because the 2 and 3 will be seen as being in a sink (void) context, as if you'd said:
($a = 1), 2, 3;
Instead, you must do something to explicitly disable or subvert the item assignment interpretation:
$a = [1, 2, 3]; # force construction (probably best practice)
$a = (1, 2, 3); # force grouping as syntactic item
$a = list 1, 2, 3; # force grouping using listop precedence
$a = @(1, 2, 3); # same thing
@$a = 1, 2, 3; # force list assignment
$a[] = 1, 2, 3; # same thing
If a function is contextually sensitive and you wish to return a scalar value, you must use item (or $ or + or ~) if you wish to force item context for either the subscript or the right side:
@a[foo()] = bar(); # foo() and bar() called in list context
@a[item foo()] = item bar(); # foo() and bar() called in item context
@a[$(foo())] = $(bar()); # same thing
@a[+foo()] = +bar(); # foo() and bar() called in numeric context
%a{~foo()} = ~bar(); # foo() and bar() called in string context
But note that the first form still works fine if foo() and bar() are item-returning functions that are not context sensitive.
In general, this will all just do what the user expects most of the time. The rest of the time item or list behavior can be forced with minimal syntax.
- List operators are all parsed consistently. As in Perl 5, to the left a list operator looks like a term, while to the right it looks like an operator that is looser than comma. Unlike in Perl 5, the difference between the list operator form and the function form is consistently indicated via whitespace between the list operator and the first argument. If there is whitespace, it is always a list operator, and the next token will be taken as the first term of the list (or if there are no terms, as the expression terminator). Any infix operator occurring where a term is expected will be misinterpreted as a term:
say + 2; # means say(+2);
If there is no whitespace, subsequent parsing depends on the syntactic category of the next item. Parentheses (with or without a dot) turn the list operator into a function call instead, and all the function's arguments must be passed inside the parentheses (except for postfix adverbs, which may follow the parentheses provided they would not attach to some other operator by the rules of precedence).
Other than parentheses, all other postfixes are disallowed immediately after a list operator, even if there are no arguments. To add a postfix to an argumentless list operator you must write it as a function call with empty parentheses:
foo.[] # ILLEGAL
foo.() # ILLEGAL
foo++ # ILLEGAL
foo().[] # legal
foo()++ # legal (if foo() is rw)
After the parentheses any postfix operators are allowed, and apply to the result of the function call. (Also note that the postfix restriction applies only to list operators; it doesn't apply to methods. It is legal to say
$foo.bar<a b c>
to mean
$foo.bar().{'a','b','c'}
because methods never assume there are arguments unless followed by parentheses or a colon.)
If the next item after the list operator is either an infix operator or a term, a syntax error is reported. [Conjecture: this may be relaxed in non-strict mode.]
Examples:
say foo + 1; say(foo(+1));
say foo $x; say(foo($x));
say foo$x; ILLEGAL, need space or parens
say foo+1; ILLEGAL, need space or parens
say foo++; ILLEGAL, need parens
say foo($bar+1),$baz say(foo($bar+1), $baz);
say foo.($bar+1),$baz ILLEGAL, need space or parens
say foo ($bar+1),$baz say(foo($bar+1, $baz));
say foo .($bar+1),$baz say(foo($_.($bar+1), $baz));
say foo[$bar+1],$baz ILLEGAL, need foo()[]
say foo.[$bar+1],$baz ILLEGAL, need foo().[]
say foo [$bar+1],$baz say(foo([$bar+1], $baz));
say foo .[$bar+1],$baz say(foo($_.[$bar+1], $baz));
say foo{$bar+1},$baz ILLEGAL, need foo(){}
say foo.{$bar+1},$baz ILLEGAL, need foo().{}
say foo {$bar+1},$baz say(foo({$bar+1}, $baz));
say foo .{$bar+1},$baz say(foo($_.{$bar+1}, $baz));
say foo<$bar+1>,$baz ILLEGAL, need foo()<>
say foo.<$bar+1>,$baz ILLEGAL, need foo().<>
say foo <$bar+1>,$baz say(foo(<$bar+1>, $baz));
say foo .<$bar+1>,$baz say(foo($_.<$bar+1>, $baz));
Note that Perl 6 is making a consistent three-way distinction between term vs postfix vs infix, and will interpret an overloaded character like < accordingly:
From t/spec/S03-operators/list-quote-junction.t lines 37–54: (skip)
-
| # L<S03/Changes to Perl 5 operators/"Note that Perl 6 is making a consistent">
|
|
|
| my @matching_strings = <foo bar>;
|
| my @nonmatching_strings = ('fo','foo ', 'foo bar baz', 'oo', 'bar b', 'bar baz');
|
|
|
| plan 16;
|
|
|
| for @matching_strings -> $str {
|
| ok( $str ~~ (any <foo bar baz>), "'$str' matches any <foo bar baz>" );
|
| ok( $str ~~ any(<foo bar baz>), "'$str' matches any(<foo bar baz>)" );
|
| };
|
|
|
| for @nonmatching_strings -> $str {
|
| ok( ($str !~~ any <foo bar baz>), "'$str' does not match any <foo bar baz>" );
|
| ok( $str !~~ any(<foo bar baz>), "'$str' does not match any(<foo bar baz>)" );
|
| };
|
|
|
| # vim: ft=perl6
|
any <a b c> any('a','b','c') # term
any()<a b c> (any).{'a','b','c'} # postfix
any() < $x (any) < $x # infix
any<a b c> ILLEGAL # stealth postfix
This will seem unfamiliar and "undwimmy" to Perl 5 programmers, who are used to a grammar that sloppily hardwires a few postfix operators at the price of extensibility. Perl 6 chooses instead to mandate a whitespace dependency in order to gain a completely extensible class of postfix operators.
- A list operator's arguments are also terminated by a closure that is not followed by a comma or colon. (And a semicolon is implied if the closure is the final thing on a line. Use an "unspace" to suppress that.) This final closure may be followed by a postfix, in which case the postfix is applied to the result of the entire list operator.
- A function predeclared with an empty signature is considered 0-ary at run time but is still parsed as a list prefix operator, and looks for a following argument list, which it may reject at run time.
my sub foo () {...}
foo; # okay
foo(); # okay
foo (),(),(); # okay
foo 1; # fails to dispatch
The compiler is allowed to complain about anything it knows cannot succeed at run time. Note that a multi may contain () as one of its signatures, however:
my multi foo () {...}
my multi foo ($x) {...}
foo; # okay
foo(); # okay
foo (),(),(); # okay
foo 1; # okay
To declare an item that is parsed as a simple term, you must use the form term:<foo>, or some other form of constant declaration such as an enum declaration. Such a term never looks for its arguments, is never considered a list prefix operator, and may not work with subsequent parentheses because it will be parsed as a function call instead of the intended term. (The function in question may or may not exist.) For example, rand is a simple term in Perl 6 and does not allow parens, because there is no rand() function (though there's a $n.rand method). Most constant values such as e and pi are in the same category. After parsing one of these the parser expects to see a postfix or an infix operator, not a term. Therefore any attempt to use a simple value as a list operator is destined to fail with an error indicating the parser saw two terms in a row.
For those values (such as types) that do respond to parentheses (that is, that do the Callable role), the parentheses (parsed as a postfix operator) are required in order to invoke the object:
my $i = Int.($x); # okay
my $i = Int($x); # okay
my $i = Int $x; # ILLEGAL, two terms in a row
- A non-multi sub predeclared with an arity of exactly 1 also still parses as a list prefix operator expecting multiple arguments. You must explicitly use the form
prefix:<foo> to declare foo as a named unary in precedence; it must still take a single positional parameter (though any number of named parameters are allowed, which can be bound to adverbs). All other subs with arguments parse as list operators.
- The
&& and || operators are smarter about list context and return () on failure in list context rather than Bool::False. The operators still short-circuit, but if either operator would return a false value, it is converted to the null list in list context so that the false results are self-deleting. (If this self-deleting behavior is not desired, put the expression into item context rather than list context.) This self-deletion is a behavior of the operators themselves, not a general property of boolean values in list context, so either of:
@foo = so($a||$b);
@foo = not($a||$b);
is guaranteed to insert exactly one boolean value into @foo.
From t/spec/S03-junctions/associative.t lines 16–39: (skip)
-
| # L<S03/"Junctive operators">
|
| # L<S09/"Junctions">
|
| {
|
|
|
| is('1 2 3', jv((1|2)|3), "Left-associative any, | operator");
|
| is('1 2 3', jv(1|(2|3)), "Right-associative any, | operator");
|
|
|
| is('1 2 3', jv(any(any(1,2),3)), "Left-associative any()");
|
| is('1 2 3', jv(any(1,any(2,3))), "Right-associative any()");
|
|
|
| is('1 2 3', jv((1&2)&3), "Left-associative all, & operator");
|
| is('1 2 3', jv(1&(2&3)), "Right-associative all, & operator");
|
|
|
| is('1 2 3', jv(all(all(1,2),3)), "Left-associative all()");
|
| is('1 2 3', jv(all(1,all(2,3))), "Right-associative all()");
|
|
|
| is('1 2 3', jv(none(none(1,2),3)), "Left-associative none()");
|
| is('1 2 3', jv(none(1,none(2,3))), "Right-associative none()");
|
|
|
| }
|
|
|
| done_testing();
|
|
|
| # vim: ft=perl6
|
From t/spec/S03-junctions/misc.t lines 20–157: (skip)
-
| # L<S03/Junctive operators>
|
| # L<S09/Junctions>
|
| {
|
|
|
| # initialize them all to empty strings
|
| my $a = '';
|
| my $b = '';
|
| my $c = '';
|
|
|
| # make sure they all match to an empty string
|
| jok('' eq ($a & $b & $c), 'junction of ($a & $b & $c) matches an empty string');
|
| jok('' eq all($a, $b, $c), 'junction of all($a, $b, $c) matches an empty string');
|
|
|
| # give $a a value
|
| $a = 'a';
|
|
|
| # make sure that at least one of them matches 'a'
|
| jok('a' eq ($b | $c | $a), 'junction of ($b | $c | $a) matches at least one "a"');
|
| jok('a' eq any($b, $c, $a), 'junction of any($b, $c, $a) matches at least one "a"');
|
|
|
| jok('' eq ($b | $c | $a), 'junction of ($b | $c | $a) matches at least one empty string');
|
| jok('' eq any($b, $c, $a), 'junction of any($b, $c, $a) matches at least one empty string');
|
|
|
| # make sure that ~only~ one of them matches 'a'
|
| jok('a' eq ($b ^ $c ^ $a), 'junction of ($b ^ $c ^ $a) matches at ~only~ one "a"');
|
| jok('a' eq one($b, $c, $a), 'junction of one($b, $c, $a) matches at ~only~ one "a"');
|
|
|
| # give $b a value
|
| $b = 'a';
|
|
|
| # now this will fail
|
| jok('a' ne ($b ^ $c ^ $a), 'junction of ($b ^ $c ^ $a) matches at more than one "a"');
|
|
|
| # change $b and give $c a value
|
| $b = 'b';
|
| $c = 'c';
|
|
|
| jok('a' eq ($b ^ $c ^ $a), 'junction of ($b ^ $c ^ $a) matches at ~only~ one "a"');
|
| jok('b' eq ($a ^ $b ^ $c), 'junction of ($a ^ $b ^ $c) matches at ~only~ one "b"');
|
| jok('c' eq ($c ^ $a ^ $b), 'junction of ($c ^ $a ^ $b) matches at ~only~ one "c"');
|
|
|
| jok('a' eq ($b | $c | $a), 'junction of ($b | $c | $a) matches at least one "a"');
|
| jok('b' eq ($a | $b | $c), 'junction of ($a | $b | $c) matches at least one "b"');
|
| jok('c' eq ($c | $a | $b), 'junction of ($c | $a | $b) matches at least one "c"');
|
|
|
| ok(not(('a' eq ($b | $c | $a)) === Bool::False), 'junctional comparison doesn not mistakenly return both true and false');
|
| ok(not(('b' eq ($a | $b | $c)) === Bool::False), 'junctional comparison doesn not mistakenly return both true and false');
|
| ok(not(('c' eq ($c | $a | $b)) === Bool::False), 'junctional comparison doesn not mistakenly return both true and false');
|
|
|
| # test junction to junction
|
| jok(('a' | 'b' | 'c') eq ($a & $b & $c), 'junction ("a" | "b" | "c") matches junction ($a & $b & $c)');
|
| jok(('a' & 'b' & 'c') eq ($a | $b | $c), 'junction ("a" & "b" & "c") matches junction ($a | $b | $c)');
|
|
|
| # mix around variables and literals
|
|
|
| jok(($a & 'b' & 'c') eq ('a' | $b | $c), 'junction ($a & "b" & "c") matches junction ("a" | $b | $c)');
|
| jok(($a & 'b' & $c) eq ('a' | $b | 'c'), 'junction ($a & "b" & $c) matches junction ("a" | $b | "c")');
|
|
|
| }
|
|
|
| # same tests, but with junctions as variables
|
| {
|
| # initialize them all to empty strings
|
| my $a = '';
|
| my $b = '';
|
| my $c = '';
|
|
|
| my $all_of_them = $a & $b & $c;
|
| jok('' eq $all_of_them, 'junction variable of ($a & $b & $c) matches and empty string');
|
|
|
| $a = 'a';
|
|
|
| my $any_of_them = $b | $c | $a;
|
| jok('a' eq $any_of_them, 'junction variable of ($b | $c | $a) matches at least one "a"');
|
| jok('' eq $any_of_them, 'junction variable of ($b | $c | $a) matches at least one empty string');
|
|
|
| my $one_of_them = $b ^ $c ^ $a;
|
| jok('a' eq $one_of_them, 'junction variable of ($b ^ $c ^ $a) matches at ~only~ one "a"');
|
|
|
| $b = 'a';
|
|
|
| {
|
| my $one_of_them = $b ^ $c ^ $a;
|
| jok('a' ne $one_of_them, 'junction variable of ($b ^ $c ^ $a) matches at more than one "a"');
|
| }
|
|
|
| $b = 'b';
|
| $c = 'c';
|
|
|
| {
|
| my $one_of_them = $b ^ $c ^ $a;
|
| jok('a' eq $one_of_them, 'junction of ($b ^ $c ^ $a) matches at ~only~ one "a"');
|
| jok('b' eq $one_of_them, 'junction of ($a ^ $b ^ $c) matches at ~only~ one "b"');
|
| jok('c' eq $one_of_them, 'junction of ($c ^ $a ^ $b) matches at ~only~ one "c"');
|
| }
|
|
|
| {
|
| my $any_of_them = $b | $c | $a;
|
| jok('a' eq $any_of_them, 'junction of ($b | $c | $a) matches at least one "a"');
|
| jok('b' eq $any_of_them, 'junction of ($a | $b | $c) matches at least one "b"');
|
| jok('c' eq $any_of_them, 'junction of ($c | $a | $b) matches at least one "c"');
|
| }
|
|
|
| }
|
|
|
| {
|
| my $j = 1 | 2;
|
| $j = 5;
|
| is($j, 5, 'reassignment of junction variable');
|
| }
|
|
|
| {
|
| my $j;
|
| my $k;
|
| my $l;
|
|
|
| $j = 1|2;
|
| #?rakudo 3 todo 'lower case junction type'
|
| is(~WHAT($j), 'junction()', 'basic junction type reference test');
|
|
|
| $k=$j;
|
| is(~WHAT($k), 'junction()', 'assignment preserves reference');
|
|
|
| # XXX does this next one make any sense?
|
| $l=\$j;
|
| is(~WHAT($l), 'junction()', 'hard reference to junction');
|
| }
|
|
|
|
|
| =begin description
|
|
|
| Tests junction examples from Synopsis 03
|
|
|
| j() is used to convert a junction to canonical string form, currently
|
| just using .perl until a better approach presents itself.
|
|
|
| =end description
|
|
|
From t/spec/S03-junctions/misc.t lines 158–163: (skip)
-
| # L<S03/Junctive operators>
|
|
|
| # Canonical stringification of a junction
|
| sub j (Mu $j) { return $j.perl }
|
|
|
| {
|
From t/spec/S03-junctions/boolean-context.t lines 5–79: (skip)
-
| # L<S03/Junctive operators/>
|
|
|
| my $undef = Mu; $undef.defined();
|
|
|
| ok ?any(1..2), 'any(1..2) in boolean context';
|
| ok !(any(0,0)), 'any(0,0) in boolean context';
|
| ok !(one(1..2)), 'one(1..2) in boolean context';
|
| ok ?(1|2), '1|2 in boolean context';
|
| ok !(1^2), '1^2 in boolean context';
|
| ok !($undef|0), 'undef|0 in boolean context';
|
| ok !($undef|$undef), 'undef|undef in boolean context';
|
| ok !($undef), 'undef in boolean context';
|
| ok !(defined $undef), 'defined undef in boolean context';
|
| ok !(all($undef, $undef)), 'all(undef, undef) in boolean context';
|
| ok ?all(1,1), 'all(1,1) in boolean context';
|
| ok !(all(1,$undef)), 'all(1,undef) in boolean context';
|
|
|
| ok ?(1 | $undef), '1|undef in boolean context';
|
| ok ?($undef | 1), 'undef|1 in boolean context';
|
| ok !(1 & $undef), '1&undef in boolean context';
|
| ok !($undef & 1), 'undef&1 in boolean context';
|
| ok ?(1 ^ $undef), '1^undef in boolean context';
|
| ok ?($undef ^ 1), 'undef^1 in boolean context';
|
|
|
| ok ?(-1 | $undef), '-1|undef in boolean context';
|
| ok ?($undef | -1), 'undef|-1 in boolean context';
|
| ok !(-1 & $undef), '-1&undef in boolean context';
|
| ok !($undef & -1), 'undef&-1 in boolean context';
|
| ok ?(-1 ^ $undef), '-1^undef in boolean context';
|
| ok ?($undef ^ -1), 'undef^-1 in boolean context';
|
|
|
| #?DOES 3
|
| {
|
| (1|$undef && pass '1|undef in boolean context') || flunk '1|undef in boolean context';
|
| (1 & $undef && flunk '1&undef in boolean context') || pass '1&undef in boolean context';
|
| (1^$undef && pass '1^undef in boolean context') || flunk '1^undef in boolean context';
|
| }
|
|
|
| ok !(0 | $undef), '0|undef in boolean context';
|
| ok !($undef | 0), 'undef|0 in boolean context';
|
| ok !(0 & $undef), '0&undef in boolean context';
|
| ok !($undef & 0), 'undef&0 in boolean context';
|
| ok !(0 ^ $undef), '0^undef in boolean context';
|
| ok !($undef ^ 0), 'undef^0 in boolean context';
|
|
|
| {
|
| (0 | $undef && flunk '0|undef in boolean context') || pass '0|undef in boolean context';
|
| (0 & $undef && flunk '0&undef in boolean context') || pass '0&undef in boolean context';
|
| (0 ^ $undef && flunk '0^undef in boolean context') || pass '0^undef in boolean context';
|
| }
|
|
|
| #?DOES 1
|
| #?rakudo skip 'Mu and =='
|
| ok ?(0|$undef == 0), '0|undef == 0 in boolean context';
|
|
|
| my $message1 = 'boolean context collapses junctions';
|
| my $message2 = '...so that they\'re not junctions anymore';
|
| ok ?(Bool::True & Bool::False) == Bool::False, $message1;
|
| #?DOES 1
|
| #?rakudo skip 'junction type (lower case)'
|
| ok ?(Bool::True & Bool::False) !~~ junction, $message2;
|
| ok !(Bool::True & Bool::False) == Bool::True, $message1;
|
| #?DOES 1
|
| #?rakudo skip 'junction type (lower case)'
|
| ok !(Bool::True & Bool::False) !~~ junction, $message2;
|
| #?rakudo todo 'named unary as function call'
|
| ok so(Bool::True & Bool::False) == Bool::False, $message1;
|
| #?rakudo skip 'junction type (lower case)'
|
| ok so(Bool::True & Bool::False) !~~ junction, $message2;
|
| #?rakudo todo 'not does not enforce boolean context?'
|
| ok not(Bool::True & Bool::False) == Bool::True, $message1;
|
| #?rakudo skip 'junction type (lower case)'
|
| ok not(Bool::True & Bool::False) !~~ junction, $message2;
|
|
|
| # vim: ft=perl6
|
|, &, and ^ are no longer bitwise operators (see "Changes to Perl 5 operators") but now serve a much higher cause: they are now the junction constructors.
A junction is a single value that is equivalent to multiple values. They thread through operations, returning another junction representing the result:
From t/spec/S03-operators/misc.t lines 69–90: (skip)
-
| # L<S03/Junctive operators/They thread through operations>
|
| ok(?((all((4|5|6) + 3) == one(7|8|9))), "all elements in junction are incremented");
|
| ok(?((any(1..6) == one(1|2|3|4|5|6))), "any elements will match via junction");
|
|
|
| {
|
| ok( ?(7 > any(4..12)), "any test against scalar" );
|
|
|
| my @oldval = (5, 8, 12);
|
|
|
| my @newval1 = (17, 15, 14); # all greater
|
| my @newval2 = (15, 7, 20); # some less some greater
|
| my @newval3 = (3, 1, 4); # all less
|
| my @newval4 = (1,2,40);
|
|
|
| ok( ?(any(@newval4) > any(@oldval)), "any test array against any array" );
|
| ok( ?(any(@newval4) > all(@oldval)), "any test array against all array" );
|
| ok( ?(all(@newval2) > any(@oldval)), "all test array against any array" );
|
| ok( ?(all(@newval1) > all(@oldval)), "all test array against all array" );
|
|
|
| ok(?(42 > 12 & 20 & 32), "test the all infix operator");
|
| }
|
|
|
From t/spec/S03-junctions/misc.t lines 164–174: (skip)
-
| # L<S03/Junctive operators/They thread through operations>
|
| my $got;
|
| my $want;
|
| $got = ((1|2|3)+4);
|
| $want = (5|6|7);
|
| is( j($got), j($want), 'thread + returning junctive result');
|
|
|
| $got = ((1|2) + (3&4));
|
| $want = ((4|5) & (5|6));
|
| is( j($got), j($want), 'thread + returning junctive combination of results');
|
|
|
From t/spec/S03-junctions/misc.t lines 240–405: (skip)
-
| # L<S03/Junctive operators/They thread through operations>
|
|
|
| {
|
| my @subs = (sub {3}, sub {2});
|
|
|
| my $got;
|
| my $want;
|
|
|
| is(j(any(@subs)()), j(3|2), '.() on any() junction of subs');
|
|
|
| $want = (3&2);
|
| $got = all(@subs)();
|
| is(j($got), j($want), '.() on all() junction of subs');
|
|
|
| $want = (3^2);
|
| $got = one(@subs)();
|
| is(j($got), j($want), '.() on one() junction of subs');
|
|
|
| $want = none(3,2);
|
| $got = none(@subs)();
|
| is(j($got), j($want), '.() on none() junction of subs');
|
|
|
| $want = one( any(3,2), all(3,2) );
|
| $got = one( any(@subs), all(@subs) )();
|
| is(j($got), j($want), '.() on complex junction of subs');
|
|
|
| # Avoid future constant folding
|
| #my $rand = rand;
|
| #my $zero = int($rand-$rand);
|
| #my @subs = (sub {3+$zero}, sub {2+$zero});
|
| }
|
|
|
| # Check functional and operator versions produce the same structure
|
| {
|
| is(j((1|2)^(3&4)), j(one(any(1,2),all(3,4))),
|
| '((1|2)^(3&4)) equiv to one(any(1,2),all(3,4))');
|
|
|
| is(j((1|2)&(3&4)), j(all(any(1,2),all(3,4))),
|
| '((1|2)&(3&4)) equiv to all(any(1,2),all(3,4))');
|
|
|
| is(j((1|2)|(3&4)), j(any(any(1,2),all(3,4))),
|
| '((1|2)|(3&4)) equiv to any(any(1,2),all(3,4))');
|
| }
|
|
|
| # junction in boolean context
|
| ok(?(0&0) == ?(0&&0), 'boolean context');
|
| ok(?(0&1) == ?(0&&1), 'boolean context');
|
| ok(?(1&1) == ?(1&&1), 'boolean context');
|
| ok(?(1&0) == ?(1&&0), 'boolean context');
|
| ok(!(?(0&0) != ?(0&&0)), 'boolean context');
|
| ok(!(?(0&1) != ?(0&&1)), 'boolean context');
|
| ok(!(?(1&1) != ?(1&&1)), 'boolean context');
|
| ok(!(?(1&0) != ?(1&&0)), 'boolean context');
|
|
|
|
|
| {
|
| my $c = 0;
|
| if 1 == 1 { $c++ }
|
| is $c, 1;
|
| if 1 == 1|2 { $c++ }
|
| is $c, 2;
|
| if 1 == 1|2|3 { $c++ }
|
| is $c, 3;
|
|
|
| $c++ if 1 == 1;
|
| is $c, 4;
|
| $c++ if 1 == 1|2;
|
| is $c, 5, 'if modifier with junction should be called once';
|
|
|
| $c = 0;
|
| $c++ if 1 == 1|2|3;
|
| is $c, 1, 'if modifier with junction should be called once';
|
|
|
| $c = 0;
|
| $c++ if 1 == any(1, 2, 3);
|
| is $c, 1, 'if modifier with junction should be called once';
|
| }
|
|
|
| {
|
| my @array = <1 2 3 4 5 6 7 8>;
|
| jok( all(@array) == one(@array), "all(@x) == one(@x) tests uniqueness(+ve)" );
|
|
|
| push @array, 6;
|
| jok( !( all(@array) == one(@array) ), "all(@x) == one(@x) tests uniqueness(-ve)" );
|
|
|
| }
|
|
|
| # used to be a rakudo regression (RT #60886)
|
| ok Mu & Mu ~~ Mu, 'Mu & Mu ~~ Mu works';
|
|
|
| ## See also S03-junctions/autothreading.t
|
| #?rakudo skip 'substr on junctions'
|
| {
|
| is substr("abcd", 1, 2), "bc", "simple substr";
|
| my $res = substr(any("abcd", "efgh"), 1, 2);
|
| isa_ok $res, "junction";
|
| ok $res eq "bc", "substr on junctions: bc";
|
| ok $res eq "fg", "substr on junctions: fg";
|
| }
|
|
|
| #?rakudo skip 'substr on junctions'
|
| {
|
| my $res = substr("abcd", 1|2, 2);
|
| isa_ok $res, "junction";
|
| ok $res eq "bc", "substr on junctions: bc";
|
| ok $res eq "cd", "substr on junctions: cd";
|
| }
|
|
|
| #?rakudo skip 'substr on junctions'
|
| {
|
| my $res = substr("abcd", 1, 1|2);
|
| isa_ok $res, "junction";
|
| ok $res eq "bc", "substr on junctions: bc";
|
| ok $res eq "b", "substr on junctions: b";
|
| }
|
|
|
| #?rakudo skip 'index on junctions'
|
| {
|
| my $res = index(any("abcd", "qwebdd"), "b");
|
| isa_ok $res, "junction";
|
| ok $res == 1, "index on junctions: 1";
|
| ok $res == 3, "index on junctions: 3";
|
| }
|
|
|
| #?rakudo skip 'index on junctions'
|
| {
|
| my $res = index("qwebdd", "b"|"w");
|
| isa_ok $res, "junction";
|
| ok $res == 1, "index on junctions: 1";
|
| ok $res == 3, "index on junctions: 3";
|
| }
|
|
|
| # RT #63686
|
| {
|
| lives_ok { try { for any(1,2) -> $x {}; } },
|
| 'for loop over junction in try block';
|
|
|
| sub rt63686 {
|
| for any(1,2) -> $x {};
|
| return 'happiness';
|
| }
|
| is rt63686(), 'happiness', 'for loop over junction in sub';
|
| }
|
|
|
| # RT#67866: [BUG] [LHF] Error with stringifying .WHAT on any junctions
|
| #?rakudo skip 'lower case junction'
|
| {
|
| ok((WHAT any()) === junction, "test WHAT on empty any junction");
|
| ok(any().WHAT === junction, "test WHAT on empty any junction");
|
| ok((WHAT any(1,2)) === junction, "test WHAT on any junction");
|
| ok(any(1,2).WHAT === junction, "test WHAT on any junction");
|
| }
|
|
|
| # Any list has junction methods
|
| {
|
| jok(5 < (6,7,8).all, '.all method builds "all" junction');
|
| jok(!(7 < (6,7,8).all), '.all method builds "all" junction');
|
| jok(7 == (6,7,8).one, '.one method builds "one" junction');
|
| jok(9 == (6,7,8).none, '.none method builds "none" junction');
|
|
|
| my @x = (6,7,8);
|
| jok(5 < @x.all, '.all method works on array objects');
|
| }
|
| done_testing();
|
|
|
| # vim: ft=perl6
|
(1|2|3) + 4; # 5|6|7
(1|2) + (3&4); # (4|5) & (5|6)
As illustrated by the last example, when two junctions are applied through a single operator, the result is a junction representing the application of the operator to each possible combination of values.
Junctions come with the functional variants any, all, one, and none.
This opens doors for constructions like:
From t/spec/S03-junctions/misc.t lines 175–198: (skip)
-
| # L<S03/Junctive operators/This opens doors for constructions like>
|
| # unless $roll == any(1..6) { print "Invalid roll" }
|
| my $roll;
|
| my $note;
|
| $roll = 3; $note = '';
|
| unless $roll == any(1..6) { $note = "Invalid roll"; };
|
| is($note, "", 'any() junction threading ==');
|
|
|
| $roll = 7; $note = '';
|
| unless $roll == any(1..6) { $note = "Invalid roll"; };
|
| is($note, "Invalid roll", 'any() junction threading ==');
|
|
|
| # if $roll == 1|2|3 { print "Low roll" }
|
| $roll = 4; $note = '';
|
| if $roll == 1|2|3 { $note = "Low roll" }
|
| is($note, "", '| junction threading ==');
|
|
|
| $roll = 2; $note = '';
|
| if $roll == 1|2|3 { $note = "Low roll" }
|
| is($note, "Low roll", '| junction threading ==');
|
| }
|
|
|
| #?rakudo skip 'Junctions as subscripts'
|
| {
|
unless $roll == any(1..6) { print "Invalid roll" }
if $roll == 1|2|3 { print "Low roll" }
Junctions work through subscripting:
From t/spec/S03-junctions/misc.t lines 199–214: (skip)
-
| # L<S03/Junctive operators/Junctions work through subscripting>
|
| my $got;
|
| my @foo;
|
| $got = ''; @foo = ();
|
| $got ~= 'y' if try { @foo[any(1,2,3)] };
|
| is($got, '', "junctions work through subscripting, 0 matches");
|
|
|
| $got = ''; @foo = (0,1);
|
| $got ~= 'y' if try { @foo[any(1,2,3)] };
|
| is($got, '', "junctions work through subscripting, 1 match");
|
|
|
| $got = ''; @foo = (1,1,1);
|
| $got ~= 'y' if try { @foo[any(1,2,3)] };
|
| is($got, '', "junctions work through subscripting, 3 matches");
|
|
|
|
|
doit() if @foo[any(1,2,3)]
Junctions are specifically unordered. So if you say
From t/spec/S03-junctions/misc.t lines 215–239: (skip)
-
| # L<S03/Junctive operators/Junctions are specifically unordered>
|
| # Compiler *can* reorder and parallelize but *may not* so don't test
|
| # for all(@foo) {...};
|
|
|
| # Not sure what is expected
|
| #my %got = ('1' => 1); # Hashes are unordered too
|
| #@foo = (2,3,4);
|
| #for all(@foo) { %got{$_} = 1; };
|
| #is( %got.keys.sort.join(','), '1,2,3,4',
|
| # 'for all(...) { ...} as parallelizable');
|
| }
|
|
|
| =begin description
|
|
|
| These are implemented but still awaiting clarification on p6l.
|
|
|
| On Fri, 2005-02-11 at 10:46 +1100, Damian Conway wrote:
|
| > Subject: Re: Fwd: Junctive puzzles.
|
| >
|
| > Junctions have an associated boolean predicate that's preserved across
|
| > operations on the junction. Junctions also implicitly distribute across
|
| > operations, and rejunctify the results.
|
|
|
| =end description
|
|
|
foo() | bar() | baz() == 42
it indicates to the compiler that there is no coupling between the junctional arguments. They can be evaluated in any order or in parallel. They can short-circuit as soon as any of them return 42, and not run the others at all. Or if running in parallel, the first successful thread may terminate the other threads abruptly. In general you probably want to avoid code with side effects in junctions.
Use of negative operators with junctions is potentially problematic if autothreaded naively. However, by defining != and ne in terms of the negation metaoperator, we automatically get the "not raising" that is expected by an English speaker. That is
From t/spec/S03-junctions/autothreading.t lines 289–302: (skip)
-
| # L<S03/Junctive operators/Use of negative operators with junctions>
|
| {
|
| my Mu $x = 'a' ne ('a'|'b'|'c');
|
| ok $x ~~ Bool, 'infix:<ne> collapses the junction (1)';
|
| ok $x !~~ Junction, 'infix:<ne> collapses the junction (2)';
|
| nok $x, '... and the result is False';
|
|
|
| my Mu $y = 'a' !eq ('a'|'b'|'c');
|
| ok $y ~~ Bool, 'infix:<!eq> collapses the junction (1)';
|
| ok $y !~~ Junction, 'infix:<!eq> collapses the junction (2)';
|
| nok $y, '... and the result is False';
|
| }
|
|
|
| # vim: ft=perl6
|
if $a != 1 | 2 | 3 {...}
really means
if $a ![==] 1 | 2 | 3 {...}
which the metaoperator rewrites to a higher-order function resembling something like:
negate((* == *), $a, (1|2|3));
which ends up being equivalent to:
if not $a == 1 | 2 | 3 {...}
which is the semantics an English speaker expects. However, it may well be better style to write the latter form yourself.
Junctive methods on arrays, lists, and sets work just like the corresponding list operators. However, junctive methods on a hash make a junction of only the hash's keys. Use the listop form (or an explicit .pairs) to make a junction of pairs.
From t/spec/S03-operators/equality.t lines 11–42: (skip)
-
| #L<S03/Comparison semantics>
|
|
|
| # string equality & inequality
|
| ok("a" eq "a", "eq true");
|
| ok(!("a" eq "ab"), "eq false");
|
| ok("a" ne "ab", "ne true");
|
| ok(!("a" ne "a"), "ne false");
|
|
|
| # potential problem cases
|
| ok("\0" eq "\0", "eq on strings with null chars");
|
| ok(!("\0" eq "\0\0"), "!eq on strings with null chars but different lengths");
|
| ok(!("a" eq "a\0"), "eq doesn't have null-padding semantics");
|
| ok(!("a" eq "a "), "eq doesn't have space-padding semantics");
|
| ok("a" ne "a\0", "ne doesn't have null-padding semantics");
|
| ok("a" ne "a ", "ne doesn't have space-padding semantics");
|
|
|
| # string context on undefined values
|
| my $foo;
|
| #?rakudo todo "+Any() doesn't work yet"
|
| ok($foo eq "", "Any eq ''");
|
| ok($foo ne "f", "Any ne 'f'");
|
|
|
| my @foo;
|
| ok(@foo[0] eq "", "Array Any eq ''");
|
| ok(@foo[0] ne "f", "Array Any ne 'f'");
|
|
|
| # numeric equality & inequality
|
| ok(2 == 2, "== true");
|
| ok(!(2 == 3), "== false");
|
| ok(2 != 3, "!= true");
|
| ok(!(2 != 2), "!= false");
|
|
|
From t/spec/S03-operators/comparison-simple.t lines 8–44: (skip)
-
| #L<S03/Comparison semantics>
|
|
|
| # spaceship comparisons (Num)
|
| is(1 <=> 1, 0, '1 <=> 1 is same');
|
| is(1 <=> 2, -1, '1 <=> 2 is increase');
|
| is(2 <=> 1, 1, '2 <=> 1 is decrease');
|
| is('a' <=> '1', -1, '<=> is in numeric context');
|
|
|
| is 0 <=> -1, 1, '0 <=> -1 is increase';
|
| is -1 <=> 0, -1, '-1 <=> 0 is decrease';
|
| is 0 <=> -1/2, 1, '0 <=> -1/2 is increase';
|
| is 0 <=> 1/2, -1, '0 <=> 1/2 is increase';
|
| is -1/2 <=> 0, -1, '-1/2 <=> 0 is decrease';
|
| is 1/2 <=> 0, 1, '1/2 <=> 0 is decrease';
|
| is 1/2 <=> 1/2, 0, '1/2 <=> 1/2 is same';
|
| is -1/2 <=> -1/2, 0, '-1/2 <=> -1/2 is same';
|
| is 1/2 <=> -1/2, 1, '1/2 <=> -1/2 is decrease';
|
| is -1/2 <=> 1/2, -1, '-1/2 <=> 1/2 is increase';
|
|
|
| # leg comparison (Str)
|
| is('a' leg 'a', 0, 'a leg a is same');
|
| is('a' leg 'b', -1, 'a leg b is increase');
|
| is('b' leg 'a', 1, 'b leg a is decrease');
|
| is('a' leg 1, 1, 'leg is in string context');
|
|
|
| # cmp comparison
|
| is('a' cmp 'a', 0, 'a cmp a is same');
|
| is('a' cmp 'b', -1, 'a cmp b is increase');
|
| is('b' cmp 'a', 1, 'b cmp a is decrease');
|
| is(1 cmp 1, 0, '1 cmp 1 is same');
|
| is(1 cmp 2, -1, '1 cmp 2 is increase');
|
| is(2 cmp 1, 1, '2 cmp 1 is decrease');
|
| is('a' cmp 1, 1, '"a" cmp 1 is decrease'); # unspecced but P5 behavior
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
From t/spec/S03-operators/comparison.t lines 13–57: (skip)
-
| #L<S03/Comparison semantics>
|
|
|
| # spaceship comparisons (Num)
|
| is(1 <=> 1, Order::Same, '1 <=> 1 is same');
|
| is(1 <=> 2, Order::Increase, '1 <=> 2 is increase');
|
| is(2 <=> 1, Order::Decrease, '2 <=> 1 is decrease');
|
| is('a' <=> '1', Order::Increase, '<=> is in numeric context');
|
|
|
| is 0 <=> -1, Order::Decrease, '0 <=> -1 is increase';
|
| is -1 <=> 0, Order::Increase, '-1 <=> 0 is decrease';
|
| #?rakudo 2 todo 'RT 70664'
|
| is 0 <=> -1/2, Order::Decrease, '0 <=> -1/2 is increase';
|
| is 0 <=> 1/2, Order::Increase, '0 <=> 1/2 is increase';
|
| #?rakudo 6 skip 'No suitable candidate found for cmp_num, with signature PP->I'
|
| is -1/2 <=> 0, Order::Increase, '-1/2 <=> 0 is decrease';
|
| is 1/2 <=> 0, Order::Decrease, '1/2 <=> 0 is decrease';
|
| is 1/2 <=> 1/2, Order::Same, '1/2 <=> 1/2 is same';
|
| is -1/2 <=> -1/2, Order::Same, '-1/2 <=> -1/2 is same';
|
| is 1/2 <=> -1/2, Order::Decrease, '1/2 <=> -1/2 is decrease';
|
| is -1/2 <=> 1/2, Order::Increase, '-1/2 <=> 1/2 is increase';
|
|
|
| # leg comparison (Str)
|
| is('a' leg 'a', Order::Same, 'a leg a is same');
|
| is('a' leg 'b', Order::Increase, 'a leg b is increase');
|
| is('b' leg 'a', Order::Decrease, 'b leg a is decrease');
|
| is('a' leg 1, Order::Decrease, 'leg is in string context');
|
| is("a" leg "a\0", Order::Increase, 'a leg a\0 is increase');
|
| is("a\0" leg "a\0", Order::Same, 'a\0 leg a\0 is same');
|
| is("a\0" leg "a", Order::Decrease, 'a\0 leg a is decrease');
|
|
|
| # cmp comparison
|
| is('a' cmp 'a', Order::Same, 'a cmp a is same');
|
| is('a' cmp 'b', Order::Increase, 'a cmp b is increase');
|
| is('b' cmp 'a', Order::Decrease, 'b cmp a is decrease');
|
| is(1 cmp 1, Order::Same, '1 cmp 1 is same');
|
| is(1 cmp 2, Order::Increase, '1 cmp 2 is increase');
|
| is(2 cmp 1, Order::Decrease, '2 cmp 1 is decrease');
|
| is('a' cmp 1, Order::Decrease, '"a" cmp 1 is decrease'); # unspecced but P5 behavior
|
| is("a" cmp "a\0", Order::Increase, 'a cmp a\0 is increase');
|
| is("a\0" cmp "a\0", Order::Same, 'a\0 cmp a\0 is same');
|
| is("a\0" cmp "a", Order::Decrease, 'a\0 cmp a is decrease');
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
- Perl 5's comparison operators are basically unchanged, except that they can be chained because their precedence is unified.
- Binary
=== tests immutable type and value correspondence: for two value types (that is, immutable types), tests whether they are the same value (eg. 1 === 1); for two mutable types (object types), checks whether they have the same identity value. (For most such types the identity is simply the reference itself.) It is not true that [1,2] === [1,2] because those are different Array objects, but it is true that @a === @a because those are the same Array object).
Any object type may pretend to be a value type by defining a .WHICH method which returns a value type that can be recursively compared using ===, or in cases where that is impractical, by overloading === such that the comparison treats values consistently with their "eternal" identity. (Strings are defined as values this way despite also being objects.)
Two values are never equivalent unless they are of exactly the same type. By contrast, eq always coerces to string, while == always coerces to numeric. In fact, $a eq $b really means "~$a === ~$b" and $a == $b means +$a === +$b.
Note also that, while string-keyed hashes use eq semantics by default, object-keyed hashes use === semantics, and general value-keyed hashes use eqv semantics.
- Binary
eqv tests equality much like === does, but does so with "snapshot" semantics rather than "eternal" semantics. For top-level components of your value that are of immutable types, eqv is identical in behavior to ===. For components of your value that are mutable, however, rather than comparing object identity using ===, the eqv operator tests whether the canonical representation of both subvalues would be identical if we took a snapshot of them right now and compared those (now-immutable) snapshots using ===.
From t/spec/S03-operators/eqv.t lines 6–139: (skip)
-
| # L<S03/Comparison semantics/Binary eqv tests equality much like === does>
|
| # L<S32::Basics/Any/"=item eqv">
|
|
|
| # eqv on values
|
| {
|
| ok (1 eqv 1), "eqv on values (1)";
|
| ok (0 eqv 0), "eqv on values (2)";
|
| ok !(0 eqv 1), "eqv on values (3)";
|
| }
|
|
|
| # Value types
|
| {
|
| my $a = 1;
|
| my $b = 1;
|
|
|
| ok $a eqv $a, "eqv on value types (1-1)";
|
| ok $b eqv $b, "eqv on value types (1-2)";
|
| ok $a eqv $b, "eqv on value types (1-3)";
|
| }
|
|
|
|
|
| {
|
| my $a = 1;
|
| my $b = 2;
|
|
|
| ok ($a eqv $a), "eqv on value types (2-1)";
|
| ok ($b eqv $b), "eqv on value types (2-2)";
|
| ok !($a eqv $b), "eqv on value types (2-3)";
|
| }
|
|
|
| #?rakudo skip 'binding NYI'
|
| {
|
| my @a = (1,2,3);
|
| my @b = (1,2,3);
|
|
|
| ok (\@a eqv \@a), "eqv on array references (1)";
|
| ok (\@b eqv \@b), "eqv on array references (2)";
|
| ok !(\@a eqv \@b), "eqv on array references (3)";
|
| @a := @b;
|
| ok \@a eqv \@b, '\@array of two bound arrays are eqv';
|
| }
|
|
|
| {
|
| my $a = \3;
|
| my $b = \3;
|
|
|
| ok ($a eqv $a), "eqv on scalar references (1-1)";
|
| ok ($b eqv $b), "eqv on scalar references (1-2)";
|
| ok ($a eqv $b), "eqv on scalar references (1-3)";
|
| #?rakudo skip 'infix:<!eqv>'
|
| ok (\$a !eqv \$b), "eqv on scalar references (1-4)";
|
| }
|
|
|
| #?
|
| {
|
| my $a = { 3 };
|
| my $b = { 3 };
|
|
|
| ok ($a eqv $a), "eqv on sub references (1-1)";
|
| ok ($b eqv $b), "eqv on sub references (1-2)";
|
| #?rakudo todo 'eqv on sub-refs'
|
| ok ($a eqv $b), "eqv on sub references (1-3)";
|
| ok !($a eqv { 5 }), 'eqv on sub references (1-4)';
|
| }
|
|
|
| {
|
| ok (&say eqv &say), "eqv on sub references (2-1)";
|
| ok (&map eqv &map), "eqv on sub references (2-2)";
|
| ok !(&say eqv &map), "eqv on sub references (2-3)";
|
| }
|
|
|
| {
|
| my $num = 3; my $a = \$num;
|
| my $b = \$num;
|
|
|
| ok ($a eqv $a), "eqv on scalar references (2-1)";
|
| ok ($b eqv $b), "eqv on scalar references (2-2)";
|
| ok ($a eqv $b), "eqv on scalar references (2-3)";
|
| }
|
|
|
| {
|
| ok !([1,2,3] eqv [4,5,6]), "eqv on anonymous array references (1)";
|
| ok ([1,2,3] eqv [1,2,3]), "eqv on anonymous array references (2)";
|
| ok ([] eqv []), "eqv on anonymous array references (3)";
|
| }
|
|
|
| {
|
| ok !({a => 1} eqv {a => 2}), "eqv on anonymous hash references (-)";
|
| ok ({a => 1} eqv {a => 1}), "eqv on anonymous hash references (+)";
|
| ok ({a => 2, b => 1} eqv { b => 1, a => 2}), 'order really does not matter';
|
| ok !({a => 1} eqv {a => 1, b => 2}), 'hashes: different number of pairs';
|
| }
|
|
|
| #?rakudo skip 'captures'
|
| {
|
| ok !(\3 eqv \4), "eqv on anonymous scalar references (1)";
|
| # XXX the following seems bogus nowadays
|
| #?pugs 2 todo 'bug'
|
| ok !(\3 eqv \3), "eqv on anonymous scalar references (2)";
|
| ok !(\Mu eqv \Mu), "eqv on anonymous scalar references (3)";
|
| }
|
|
|
| # Chained eqv (not specced, but obvious)
|
| {
|
| ok (3 eqv 3 eqv 3), "chained eqv (1)";
|
| ok !(3 eqv 3 eqv 4), "chained eqv (2)";
|
| }
|
|
|
| # Subparam binding doesn't affect eqv test
|
| {
|
| my $foo;
|
| my $test = -> $arg { $foo eqv $arg };
|
|
|
| $foo = 3;
|
| ok $test($foo), "subparam binding doesn't affect eqv (1)";
|
| ok $test(3), "subparam binding doesn't affect eqv (2)";
|
|
|
| ok !$test(4), "subparam binding doesn't affect eqv (3)";
|
| my $bar = 4;
|
| ok !$test($bar), "subparam binding doesn't affect eqv (4)";
|
| }
|
|
|
| {
|
| is(1 eqv 1, Bool::True, 'eqv returns Bool::True when true');
|
| is(0 eqv 1, Bool::False, 'eqv returns Bool::False when false');
|
| }
|
|
|
| {
|
| is Mu eqv Mu, Bool::True, 'Mu eqv Mu';
|
| is Any eqv Any, Bool::True, 'Any eqv Any';
|
| is Any eqv Mu, Bool::False, 'Any !eqv Mu';
|
| }
|
|
|
| # vim: ft=perl6
|
If that's not enough flexibility, there is also an eqv() function that can be passed additional information specifying how you want canonical values to be generated before comparison. This gives eqv() the same kind of expressive power as a sort signature. (And indeed, the cmp operator from Perl 5 also has a functional analog, cmp(), that takes additional instructions on how to do 3-way comparisons of the kind that a sorting algorithm wants.) In particular, a signature passed to eqv() will be bound to the two operands in question, and then the comparison will proceed on the formal parameters according to the information contained in the signature, so you can force numeric, string, natural, or other comparisons with proper declarations of the parameter's type and traits. If the signature doesn't match the operands, eqv() reverts to standard eqv comparison. (Likewise for cmp().)
- Binary
cmp is no longer the comparison operator that forces stringification. Use the leg operator for the old Perl 5 cmp semantics. The cmp is just like the eqv above except that instead of returning Bool::False or Bool::True values it always returns Order::Increase, Order::Same, or Order::Decrease (which numerify to -1, 0, or +1).
- The
leg operator (less than, equal to, or greater than) is defined in terms of cmp, so $a leg $b is now defined as ~$a cmp ~$b. The sort operator still defaults to cmp rather than leg. The <=> operator's semantics are unchanged except that it returns an Order value as described above. In other words, $a <=> $b is now equivalent to +$a cmp +$b.
From t/spec/S03-operators/spaceship-and-containers.t lines 6–18: (skip)
-
| #L<S03/Comparison semantics/The <=> operator>
|
|
|
| my %h = ("a" => 1, "b" => 2);
|
| ok(%h{"a"} < %h{"b"}, 'comparing hash values');
|
| ok(%h{"a"} <= %h{"b"}, 'comparing hash values');
|
| is(%h{"a"} <=> %h{"b"}, -1, 'comparing hash values');
|
|
|
| my @a = (1, 2);
|
| ok(@a[0] < @a[1], 'comparing array values');
|
| ok(@a[0] <= @a[1], 'comparing array values');
|
| is(@a[0] <=> @a[1], -1, 'comparing array values');
|
|
|
| # vim: ft=perl6
|
- For boolean comparison operators with non-coercive
cmp semantics, use the generic before and after infix operators. As ordinary infix operators these may be negated (!before and !after) as well as reduced ([before] and [after]).
- Infix
min and max may be used to select one or the other of their arguments. Reducing listop forms [min] and [max] are also available, as are the min= and max= assignment operators. By default min and max use cmp semantics. As with all cmp-based operators, this may be modified by an adverb specifying different semantics.
- Note that, like most other operators, a comparison naturally throws an exception if either of its arguments is undefined. However, various parallelizable contexts such as sort and hyper suppress this, er, somehow.
The .. range operator has variants with ^ on either end to indicate exclusion of that endpoint from the range. It always produces a Range object. Range objects are immutable, and primarily used for matching intervals. 1..2 is the interval from 1 to 2 inclusive of the endpoints, whereas 1^..^2 excludes the endpoints but matches any real number in between.
From t/spec/S03-operators/range.t lines 36–204: (skip)
-
| # L<S03/Range and RangeIter semantics/range operator has variants>
|
| is [1^..9], [2..9], "bottom-exclusive range (^..) works (1)";
|
| is [2^..2], [], "bottom-exclusive range (^..) works (2)";
|
| is [3^..2], [], "bottom-exclusive auto-rev range (^..) works (3)";
|
| is [1 ..^9], [1..8], "top-exclusive range (..^) works (1)";
|
| is [2 ..^2], [], "top-exclusive range (..^) works (2)";
|
| is [3 ..^2], [], "top-exclusive auto-rev range (..^) works (3)";
|
| is [1^..^9], [2..8], "double-exclusive range (^..^) works (1)";
|
| is [9^..^1], [], "double-exclusive auto-rev range (^..^) works (2)";
|
| is [1^..^2], [], "double-exclusive range (^..^) can produce null range (1)";
|
|
|
| # tests of (x ^..^ x) here and below ensure that our implementation
|
| # of double-exclusive range does not blindly remove an element
|
| # from the head and tail of a list
|
| is [1^..^1], [], "double-exclusive range (x ^..^ x) where x is an int";
|
|
|
| is ["a"^.."z"], ["b".."z"], "bottom-exclusive string range (^..) works";
|
| is ["z"^.."a"], [], "bottom-exclusive string auto-rev range (^..) works";
|
| is ["a"..^"z"], ["a".."y"], "top-exclusive string range (..^) works";
|
| is ["z"..^"a"], [], "top-exclusive string auto-rev range (..^) works";
|
| is ["a"^..^"z"], ["b".."y"], "double-exclusive string range (^..^) works";
|
| is ["z"^..^"a"], [], "double-exclusive string auto-rev range (^..^) works";
|
| is ['a'^..^'b'], [], "double-exclusive string range (^..^) can produce null range";
|
| is ['b'^..^'a'], [], "double-exclusive string auto-rev range (^..^) can produce null range";
|
| is ['a' ^..^ 'a'], [], "double-exclusive range (x ^..^ x) where x is a char";
|
|
|
| #?pugs todo 'bug'
|
| is 1.5 ~~ 1^..^2, Bool::True, "lazy evaluation of the range operator";
|
|
|
| # Test the unary ^ operator
|
| is ~(^5), "0 1 2 3 4", "unary ^num produces the range 0..^num";
|
| is [^1], [0], "unary ^ on the boundary ^1 works";
|
| is [^0], [], "unary ^0 produces null range";
|
| is [^-1], [], "unary ^-1 produces null range";
|
| is [^0.1], [0], "unary ^0.1 produces the range 0..^x where 0 < x < 1";
|
| is [^'a'], [], "unary ^'a' produces null range";
|
| is ~(^"5"), "0 1 2 3 4", 'unary ^"num" produces the range 0..^num';
|
|
|
| {
|
| my @a = 3, 5, 3;
|
| is (^@a).perl, (0..^3).perl, 'unary ^@a produces 0..^+@a';
|
| }
|
|
|
| # test iterating on infinite ranges
|
| is (1..*).[^5].join('|'), '1|2|3|4|5', '1..*';
|
| is ('a'..*).[^5].join('|'), 'a|b|c|d|e', '"a"..*';
|
|
|
| # test that the zip operator works with ranges
|
| is (1..5 Z <a b c>).join('|'), '1|a|2|b|3|c', 'Ranges and infix:<Z>';
|
| is (1..2 Z <a b c>).join('|'), '1|a|2|b', 'Ranges and infix:<Z>';
|
| is (<c b a> Z 1..5).join('|'), 'c|1|b|2|a|3', 'Ranges and infix:<Z>';
|
|
|
| # two ranges
|
| is (1..6 Z 'a' .. 'c').join, '1a2b3c', 'Ranges and infix:<Z>';
|
|
|
| {
|
| # Test with floats
|
| # 2006-12-05:
|
| # 16:16 <TimToady> ~(1.9 ^..^ 4.9) should produce 2.9, 3.9
|
| # 16:17 <pmichaud> and ~(1.9 ^..^ 4.5) would produce the same?
|
| # 16:17 <TimToady> yes
|
| is ~(1.1 .. 4) , "1.1 2.1 3.1", "range with float .min";
|
| is ~(1.9 .. 4) , "1.9 2.9 3.9", "range with float .min";
|
| is ~(1.1 ^.. 4), "2.1 3.1" , "bottom exclusive range of float";
|
| is ~(1.9 ^.. 4), "2.9 3.9" , "bottom exclusive range of float";
|
|
|
| is ~(1 .. 4.1) , "1 2 3 4", "range with float .max";
|
| is ~(1 .. 4.9) , "1 2 3 4", "range with float .max";
|
| is ~(1 ..^ 4.1), "1 2 3 4", "top exclusive range of float";
|
| is ~(1 ..^ 4.9), "1 2 3 4", "top exclusive range of float";
|
|
|
| is ~(1.1 .. 4.1), "1.1 2.1 3.1 4.1", "range with float .min/.max";
|
| is ~(1.9 .. 4.1), "1.9 2.9 3.9" , "range with float .min/.max";
|
| is ~(1.1 .. 4.9), "1.1 2.1 3.1 4.1", "range with float .min/.max";
|
| is ~(1.9 .. 4.9), "1.9 2.9 3.9 4.9", "range with float .min/.max";
|
|
|
| is ~(1.1 ^..^ 4.1), "2.1 3.1" , "both exclusive float range";
|
| is ~(1.9 ^..^ 4.1), "2.9 3.9" , "both exclusive float range";
|
| is ~(1.1 ^..^ 4.9), "2.1 3.1 4.1", "both exclusive float range";
|
| is ~(1.9 ^..^ 4.9), "2.9 3.9" , "both exclusive float range";
|
| is [1.1 ^..^ 1.1], [], "double-exclusive range (x ^..^ x) where x is a float";
|
| }
|
|
|
| # Test that the operands are forced to scalar context
|
| ## From pmichaud 2006-06-30: These tests may be incorrect.
|
| ## C<@one> in ## item context returns an Array, not a number
|
| ## -- use C< +@one > to get the number of elements. So, we
|
| ## need to either declare that there's a version of infix:<..>
|
| ## that coerces its arguments to numeric context, or we can
|
| ## remove these tests from the suite.
|
| #?rakudo skip 'MMD function __cmp not found for types (101, 95)'
|
| {
|
| my @three = (1, 1, 1);
|
| my @one = 1;
|
|
|
| is ~(@one .. 3) , "1 2 3", "lower inclusive limit is in scalar context";
|
| is ~(@one ^.. 3) , "2 3" , "lower exclusive limit is in scalar context";
|
| is ~(3 ^.. @one) , "" , "lower exclusive limit is in scalar context";
|
| is ~(1 .. @three) , "1 2 3", "upper inclusive limit is in scalar context";
|
| is ~(4 .. @three) , "" , "upper inclusive limit is in scalar context";
|
| is ~(1 ..^ @three) , "1 2" , "upper exclusive limit is in scalar context";
|
| is ~(4 ..^ @three) , "" , "upper exclusive limit is in scalar context";
|
| is ~(@one .. @three), "1 2 3", "both inclusive limits are in scalar context";
|
| is ~(@three .. @one), "" , "null range produced with lists forced to scalar context";
|
| is ~(@one ^..^ @three), "2" , "both exclusive limits are in scalar context";
|
| is ~(@three ^..^ @one), "" , "both exclusive limits are in scalar context";
|
| }
|
|
|
| # test that .map and .grep work on ranges
|
| {
|
| is (0..3).map({$_ * 2}).join('|'), '0|2|4|6', '.map works on ranges';
|
| is (0..3).grep({$_ == 1|3}).join('|'), '1|3', '.grep works on ranges';
|
| is (1..3).first({ $_ % 2 == 0}), 2, '.first works on ranges';
|
| is (1..3).reduce({ $^a + $^b}), 6, '.reduce works on ranges';
|
| }
|
|
|
| # test that range operands are handled in string context if strings
|
| {
|
| my $range;
|
| my $start = "100.B";
|
| my $end = "102.B";
|
| lives_ok { $range = $start..$end },
|
| 'can make range from numeric string vars';
|
| is $range.min, $start, 'range starts at start';
|
| is $range.min.WHAT, "Str()", 'range start is a string';
|
| is $range.max, $end, 'range ends at end';
|
| is $range.max.WHAT, "Str()", 'range end is a string';
|
| lives_ok { "$range" }, 'can stringify range';
|
| is ~$range, "100.B 101.B 102.B", 'range is correct';
|
| }
|
|
|
| # RT #67882
|
| {
|
| my $range;
|
| lives_ok { '1 3' ~~ /(\d+) \s (\d+)/; $range = $0..$1 },
|
| 'can make range from match vars';
|
| is $range.min, 1, 'range starts at one';
|
| is $range.max, 3, 'range ends at three';
|
| lives_ok { "$range" }, 'can stringify range';
|
| is ~$range, "1 2 3", 'range is correct';
|
| }
|
| # and another set, just for the lulz
|
| # RT #67882
|
| {
|
| ok '1 3' ~~ /(\d) . (\d)/, 'regex sanity';
|
| isa_ok $0..$1, Range, '$0..$1 constructs a Range';
|
| is ($0..$1).join('|'), '1|2|3', 'range from $0..$1';
|
| }
|
| {
|
| my $range;
|
| lives_ok { '1 3' ~~ /(\d+) \s (\d+)/; $range = +$0..+$1 },
|
| 'can make range from match vars with numeric context forced';
|
| is $range.min, 1, 'range starts at one';
|
| is $range.max, 3, 'range ends at three';
|
| lives_ok { "$range" }, 'can stringify range';
|
| is ~$range, "1 2 3", 'range is correct';
|
| }
|
| {
|
| my $range;
|
| lives_ok { '1 3' ~~ /(\d+) \s (\d+)/; $range = ~$0..~$1 },
|
| 'can make range from match vars with string context forced';
|
| is $range.min, 1, 'range starts at one';
|
| is $range.min.WHAT, "Str()", 'range start is a string';
|
| is $range.max, 3, 'range ends at three';
|
| is $range.max.WHAT, "Str()", 'range end is a string';
|
| lives_ok { "$range" }, 'can stringify range';
|
| is ~$range, "1 2 3", 'range is correct';
|
| }
|
|
|
For numeric arguments of differing type, ranges coerce to the wider type, so:
1 .. 1.5
is taken to mean:
1.0 .. 1.5
These coercions are defined by multi signatures. (Other types may have different coercion policies.) It is specifically illegal to use anything that does Iterable as implicitly numeric:
0 ..^ 10 # 0 .. 9
0 .. ^10 # ERROR
For ranges with other non-numeric types on the right, the right argument is coerced to the type of the left argument and treated as numeric. Hence, Array types in the second argument are assumed to be intended as numeric if the left argument is numeric:
0 ..^ @x # okay
0 ..^ +@x # same thing
Whatever types are also supported to represent -Inf/+Inf. If either endpoint is a WhateverCode, the range is curried into another WhateverCode.
For other types, ranges may be composed for any two arguments of the same type, if the type itself supports it. That is, in general, infix:<..>:(::T Any $x, T $y) is defined such that, if type T defines generic comparison (that is, by defining infix:<cmp> or equivalent), a range is constructed in that type. If T also defines .succ, then the range may be iterated. (Otherwise the range may only be used as an interval, and will return failure if asked for a RangeIter.) Note that it is not necessary to define a range multimethod in type T, since the generic routine can usually auto-generate the range for you.
Range objects support .min and .max methods representing their left and right arguments. The .bounds method returns both values as a two-element list representing the interval. Ranges are not autoreversing: 2..1 is always a null range. (The series operator ... can autoreverse, however. See below.)
Range objects support .excludes_min and .excludes_max methods representing the exclusion (has ^) or inclusion (no ^) of each endpoint in the Range.
Range | .min | .max | .excludes_min | .excludes_max
-----------+------+------+-------------+------------
1..10 | 1 | 10 | Bool::False | Bool::False
2.7..^9.3 | 2.7 | 9.3 | Bool::False | Bool::True
'a'^..'z' | 'a' | 'z' | Bool::True | Bool::False
1^..^10 | 1 | 10 | Bool::True | Bool::True
If used in a list context, a Range object returns an iterator that produces a series of values starting at the min and ending at the max. Either endpoint may be excluded using ^. Hence 1..2 produces (1,2) but 1^..^2 is equivalent to 2..1 and produces no values (Nil). To specify a series that counts down, use a reverse:
reverse 1..2
reverse 'a'..'z'
Alternately, for numeric sequences, you can use the series operator instead of the range operator:
100,99,98 ... 0
100 ... *-1, 0 # same thing
In other words, any Range used as a list assumes .succ semantics, never .pred semantics. No other increment is allowed; if you wish to increment a numeric sequence by some number other than 1, you must use the ... series operator. (The Range operator's :by adverb is hereby deprecated.)
0 ... *+0.1, 100 # 0, 0.1, 0.2, 0.3 ... 100
A Range may be iterated only if the type in question supports the .succ method. If it does not, any attempt to iterate returns failure.
Smart matching against a Range object smartmatches the endpoints in the domain of the object being matched, so fractional numbers are not truncated before comparison to integer ranges:
1.5 ~~ 1^..^2 # true, equivalent to 1 < 1.5 < 2
2.1 ~~ 1..2 # false, equivalent to 1 <= 2.1 <= 2
If a * (see the "Whatever" type in S02) occurs on the right side of a range, it is taken to mean "positive infinity" in whatever typespace the range is operating, as inferred from the left operand. A * on the left means "negative infinity" for types that support negative values, and the first value in the typespace otherwise as inferred from the right operand. (A star on both sides is not allowed.)
0..* # 0 .. +Inf
'a'..* # 'a' le $_
*..0 # -Inf .. 0
*..* # Illegal
1.2.3..* # Any version higher than 1.2.3.
May..* # May through December
An empty range cannot be iterated; it returns a Nil instead. An empty range still has a defined .min and .max, but one of the following is true: 1. The .min is greater than the .max. 2. The .min is equal to the .max and at least one of .excludes_min or .excludes_max is true. 3. Both .excludes_min and .excludes_max are true and .min and .max are consecutive values.
Ranges that are iterated transmute into the corresponding series operator, and hence use !after semantics to determine an end to the sequence.
The unary ^ operator generates a range from 0 up to (but not including) its argument. So ^4 is short for 0..^4.
for ^4 { say $_ } # 0, 1, 2, 3
If applied to a type name, it indicates the metaclass instance instead, so ^Moose is short for HOW(Moose) or Moose.HOW. It still kinda means "what is this thing's domain" in an abstract sort of way.
[This section is conjectural, and may be ignored for 6.0.]
Since use of Range objects in item context is usually non-sensical, a Range object used as an operand for scalar operators will generally attempt to distribute the operator to its endpoints and return another suitably modified Range instead, much like a junction of two items, only with proper interval semantics. (Notable exceptions to this autothreading include infix:<~~>, which does smart matching, and prefix:<+> which returns the length of the range.) Therefore if you wish to write a slice using a length instead of an endpoint, you can say
@foo[ start() + ^$len ]
which is short for:
@foo[ start() + (0..^$len) ]
which is equivalent to something like:
@foo[ list do { my $tmp = start(); $tmp ..^ $tmp+$len } ]
In other words, operators of numeric and other ordered types are generally overloaded to do something sensible on Range objects.
From t/spec/S02-literals/listquote.t lines 14–69: (skip)
-
| # L<S03/"Chained comparisons">
|
|
|
| my $s = join 'a', <x y z>;
|
| is($s, "xayaz", 'list context <list>');
|
|
|
| #?rakudo skip 'meta operators'
|
| #?pugs todo '|<<'
|
| {
|
| my $s = join |<< <a x y z>;
|
| is($s, "xayaz", 'listop |<< <list>');
|
| }
|
|
|
| dies_ok { [1,2,3].join<a b c> }, '.join<abc> parses but semantic error';
|
|
|
| my @y = try { ({:a<1>, :b(2)}<a b c>) };
|
| #?rakudo todo 'unknown errors'
|
| ok(@y eqv [1,2,Any], '{...}<a b c> is hash subscript');
|
|
|
| eval_dies_ok '({:a<1>, :b(2)} <a b c>)', '{...} <...> parsefail';
|
|
|
| ok( ?((1 | 3) < 3), '(...) < 3 no parsefail');
|
|
|
| #?pugs todo 'parsing bug'
|
| #?rakudo todo 'parsing'
|
| eval_dies_ok '(1 | 3)<3', '()<3 parsefail';
|
|
|
| # WRONG: should be parsed as print() < 3
|
| # eval 'print < 3';
|
| # ok($!, 'print < 3 parsefail');
|
|
|
|
|
| eval_dies_ok ':foo <1 2 3>', ':foo <1 2 3> parsefail';
|
|
|
| #?rakudo skip 'Null PMC access in can()'
|
| {
|
| my $r = eval ':foo <3';
|
| ok($r, ':foo <3 is comparison');
|
| }
|
|
|
| my $p = eval ':foo<1 2 3>';
|
| is($p, ~('foo' => (1,2,3)), ':foo<1 2 3> is pair of list');
|
|
|
| # Lists may contain newline characters
|
|
|
| {
|
| my %e = ("foo", "bar", "blah", "blah");
|
|
|
| my %foo = (
|
| "foo", "bar",
|
| "blah", "blah",
|
| );
|
| is(+%foo, +%e, "Hashes with embedded newlines in the list (1)");
|
| is(%foo<foo>, %e<foo>, "Hashes with embedded newlines in the list (2)");
|
| is(%foo<blah>, %e<blah>, "Hashes with embedded newlines in the list (3)");
|
| }
|
|
|
From t/spec/S03-operators/relational.t lines 104–141: (skip)
-
| # L<S03/"Chained comparisons">
|
|
|
| ok(5 > 4 > 3, "chained >");
|
| ok(3 < 4 < 5, "chained <");
|
| ok(5 == 5 > -5, "chained mixed = and > ");
|
| ok(!(3 > 4 < 5), "chained > and <");
|
| ok(5 <= 5 > -5, "chained <= and >");
|
| ok(-5 < 5 >= 5, "chained < and >=");
|
|
|
| is(5 > 1 < 10, 5 > 1 && 1 < 10, 'chained 5 > 1 < 10');
|
| is(5 < 1 < 10, 5 < 1 && 1 < 10, 'chained 5 < 1 < 10');
|
|
|
| ok('e' gt 'd' gt 'c', "chained gt");
|
| ok('c' lt 'd' lt 'e', "chained lt");
|
| ok('e' eq 'e' gt 'a', "chained mixed = and gt ");
|
| ok(!('c' gt 'd' lt 'e'), "chained gt and lt");
|
| ok('e' le 'e' gt 'a', "chained le and gt");
|
| ok('a' lt 'e' ge 'e', "chained lt and ge");
|
|
|
| is('e' gt 'a' lt 'j', 'e' gt 'a' && 'a' lt 'j', 'e gt a lt j');
|
| is('e' lt 'a' lt 'j', 'e' lt 'a' && 'a' lt 'j', 'e lt a lt j');
|
|
|
| ok("5" gt "4" gt "3", "5 gt 4 gt 3 chained str comparison");
|
| ok("3" lt "4" lt "5", "3 lt 4 gt 5 chained str comparison");
|
| ok(!("3" gt "4" lt "5"), "!(3 gt 4 lt 5) chained str comparison");
|
| ok("5" eq "5" gt "0", '"5" eq "5" gt "0" chained str comparison with equality');
|
| ok("5" le "5" gt "0", "5 le 5 gt 0 chained str comparison with le");
|
| ok("0" lt "5" ge "5", "0 lt 5 ge 5 chained comparison with ge");
|
|
|
| # make sure we don't have "padding" or "trimming" semantics
|
| ok("a" lt "a\0", 'a lt a\0');
|
| ok("a" lt "a ", 'a lt a\x20');
|
| ok("a\0" gt "a", 'a\0 gt a');
|
| ok("a " gt "a", 'a\x20 gt a');
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Perl 6 supports the natural extension to the comparison operators, allowing multiple operands:
if 1 < $a < 100 { say "Good, you picked a number *between* 1 and 100." }
if 3 < $roll <= 6 { print "High roll" }
if 1 <= $roll1 == $roll2 <= 6 { print "Doubles!" }
A chain of comparisons short-circuits if the first comparison fails:
From t/spec/S03-operators/short-circuit.t lines 150–178: (skip)
-
| # L<S03/Chained comparisons/A chain of comparisons short-circuits>
|
| {
|
| my $x = 0;
|
| my $y = 0;
|
| ok(not(++$x < $y++ < $y++), "chained comparison (truth - 2)");
|
| # expect x=1, y=1
|
| is($y, 1, "chained comparison short-circuit: stopping soon enough");
|
| }
|
|
|
| # a pugs regression
|
|
|
| {
|
| my $a = sub { 1 };
|
| my $b;
|
| sub c($code) { if $code and $code() { return 1 }; return 2 }
|
|
|
| is c($a), 1, 'shortcircuit idiom given coderef works';
|
|
|
| # This one will just kill pugs with the cast failure, so force fail
|
| #?pugs eval 'short circuiting'
|
| is c($b), 2, 'shortcircuit idiom given Mu works';
|
| }
|
|
|
| # a rakudo regression
|
| ok (0 || 0 || 1), '0 || 0 || 1 is true';
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
1 > 2 > die("this is never reached");
Each argument in the chain will evaluate at most once:
From t/spec/S03-operators/short-circuit.t lines 140–149: (skip)
-
| # L<S03/Chained comparisons/Each argument chain will evaluate at most once>
|
| {
|
| my $x = 0;
|
| my $y = 0;
|
| #?rakudo todo 'chained comparison order of evaluations'
|
| ok(($x++ < ++$y < ++$y), "chained comparison (truth - 1)");
|
| # expect x=1, y=2
|
| is($y, 2, "chained comparison short-circuit: not re-evaluating middle");
|
| }
|
|
|
1 > $x++ > 2 # $x increments exactly once
Note: any operator beginning with < must have whitespace in front of it, or it will be interpreted as a hash subscript instead.
Here is the table of smart matches for standard Perl 6 (that is, the dialect of Perl in effect at the start of your compilation unit). Smart matching is generally done on the current "topic", that is, on $_. In the table below, $_ represents the left side of the ~~ operator, or the argument to a given, or to any other topicalizer. X represents the pattern to be matched against on the right side of ~~, or after a when. (And, in fact, the ~~ operator works as a small topicalizer; that is, it binds $_ to the value of the left side for the evaluation of the right side. Use the underlying .ACCEPTS form to avoid this topicalization.)
The first section contains privileged syntax; if a match can be done via one of those entries, it will be. These special syntaxes are dispatched by their form rather than their type. Otherwise the rest of the table is used, and the match will be dispatched according to the normal method dispatch rules. The optimizer is allowed to assume that no additional match operators are defined after compile time, so if the pattern types are evident at compile time, the jump table can be optimized. However, the syntax of this part of the table is still somewhat privileged, insofar as the ~~ operator is one of the few operators in Perl that does not use multiple dispatch. Instead, type-based smart matches singly dispatch to an underlying method belonging to the X pattern object.
In other words, smart matches are dispatched first on the basis of the pattern's form or type (the X below), and then that pattern itself decides whether and how to pay attention to the type of the topic ($_). So the second column below is really the primary column. The Any entries in the first column indicate a pattern that either doesn't care about the type of the topic, or that picks that entry as a default because the more specific types listed above it didn't match.
$_ X Type of Match Implied Match if (given $_)
====== ===== ===================== ===================
Any True ~~ True (parsewarn)
Any False ~~ False match (parsewarn)
Any * block signature match block successfully binds to |$_
Any Callable:($) item sub truth X($_)
From t/spec/S03-smartmatch/any-callable.t lines 5–14: (skip)
-
| #L<S03/"Smart matching"/Any Callable:($) item sub truth>
|
| {
|
| sub is_even($x) { $x % 2 == 0 }
|
| sub is_odd ($x) { $x % 2 == 1 }
|
| ok 4 ~~ &is_even, 'scalar sub truth (unary)';
|
| ok 4 !~~ &is_odd, 'scalar sub truth (unary, negated smart-match)';
|
| ok !(3 ~~ &is_even), 'scalar sub truth (unary)';
|
| ok !(3 !~~ &is_odd), 'scalar sub truth (unary, negated smart-match)';
|
| }
|
|
|
Any Callable:() simple closure truth X() (ignoring $_)
From t/spec/S03-smartmatch/any-callable.t lines 15–26: (skip)
-
| #L<S03/"Smart matching"/Any Callable:() simple closure truth>
|
| {
|
| sub uhuh { 1 }
|
| sub nuhuh { Mu }
|
|
|
| ok((Mu ~~ &uhuh), "scalar sub truth");
|
| ok(!(Mu ~~ &nuhuh), "negated scalar sub false");
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Any Bool simple truth X
From t/spec/S03-smartmatch/any-bool.t lines 5–19: (skip)
-
| #L<S03/Smart matching/Any Bool simple truth>
|
| {
|
| ok (0 ~~ True), '$something ~~ True (1)';
|
| ok (0 ~~ Bool::True), '$something ~~ Bool::True (1)';
|
| ok ('a' ~~ True), '$something ~~ True (2)';
|
| ok ('a' ~~ Bool::True), '$something ~~ Bool::True (2)';
|
| ok !(0 ~~ False), '$something ~~ False (1)';
|
| ok !(0 ~~ Bool::False), '$something ~~ Bool::False (1)';
|
| ok !('a' ~~ False), '$something ~~ False (2)';
|
| ok !('a' ~~ Bool::False),'$something ~~ Bool::False (2)';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Any Numeric numeric equality +$_ == X
Any Stringy string equality ~$_ eq X
Any Whatever always matches True
Hash Pair test hash mapping $_{X.key} ~~ X.value
From t/spec/S03-smartmatch/any-hash-pair.t lines 5–19: (skip)
-
| #L<S03/Smart matching/Hash Pair test hash mapping>
|
| {
|
| my %a = (a => 1, b => 'foo', c => Mu);
|
| ok (%a ~~ b => 'foo'), '%hash ~~ Pair (Str, +)';
|
| ok !(%a ~~ b => 'ugh'), '%hash ~~ Pair (Str, -)';
|
| ok (%a ~~ a => 1.0), '%hash ~~ Pair (Num, +)';
|
| ok (%a ~~ :b<foo>), '%hash ~~ Colonpair';
|
| ok (%a ~~ c => *.notdef), '%hash ~~ Pair (.notdef, Mu)';
|
| ok (%a ~~ d => *.notdef), '%hash ~~ Pair (.notdef, Nil)';
|
| ok !(%a ~~ a => 'foo'), '%hash ~~ Pair (key and val not paired)';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Any Pair test object attribute ?."{X.key}" === ?X.value (e.g. filetests)
From t/spec/S03-smartmatch/any-pair.t lines 5–35: (skip)
-
| #L<S03/Smart matching/Any Pair test object attribute>
|
| {
|
| # ?."{X.key}" === ?X.value
|
| # means:
|
| # call the method with the name of X.key on the object, coerce to
|
| # Bool, and check if it's the same as boolean value of X.value
|
|
|
| class SmartmatchTest::AttrPair {
|
| has $.a = 4;
|
| has $.b = 'foo';
|
| has $.c = Mu;
|
| }
|
| my $o = SmartmatchTest::AttrPair.new();
|
| ok ($o ~~ :a(4)), '$obj ~~ Pair (Int, +)';
|
| ok ($o ~~ :a(2)), '$obj ~~ Pair (Int, +)';
|
| ok !($o ~~ :b(0)), '$obj ~~ Pair (different types)';
|
| ok ($o ~~ :b<foo>), '$obj ~~ Pair (Str, +)';
|
| ok ($o ~~ :b<ugh>), '$obj ~~ Pair (Str, -)';
|
| ok ($o ~~ :c(Mu)), '$obj ~~ Pair (Mu, +)';
|
| ok ($o ~~ :c(0)), '$obj ~~ Pair (0, +)';
|
| ok !($o ~~ :b(Mu)), '$obj ~~ Pair (Mu, -)';
|
| # not explicitly specced, but implied by the spec and decreed
|
| # by TimToady: non-existing method or attribute dies:
|
| # http://irclog.perlgeek.de/perl6/2009-07-06#i_1293199
|
| dies_ok {$o ~~ :e(Mu)}, '$obj ~~ Pair, nonexistent, dies (1)';
|
| dies_ok {$o ~~ :e(5)}, '$obj ~~ Pair, nonexistent, dies (2)';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Set Set identical sets $_ === X
Hash Set hash keys same set $_.keys === X
Any Set force set comparison Set($_) === X
Array Array arrays are comparable $_ «===» X (dwims * wildcards!)
From t/spec/S03-smartmatch/array-array.t lines 5–54: (skip)
-
| #L<S03/Smart matching/arrays are comparable>
|
| {
|
| ok((("blah", "blah") ~~ ("blah", "blah")), "qw/blah blah/ .eq");
|
| ok(!((1, 2) ~~ (1, 1)), "1 2 !~~ 1 1");
|
| ok(!((1, 2, 3) ~~ (1, 2)), "1 2 3 !~~ 1 2");
|
| ok(!((1, 2) ~~ (1, 2, 3)), "1 2 !~~ 1 2 3");
|
| ok(!([] ~~ [1]), "array smartmatch boundary conditions");
|
| ok(!([1] ~~ []), "array smartmatch boundary conditions");
|
| ok(([] ~~ []), "array smartmatch boundary conditions");
|
| ok(([1] ~~ [1]), "array smartmatch boundary conditions");
|
| ok((1,2,3,4) ~~ (1,*), 'array smartmatch dwims * at end');
|
| ok((1,2,3,4) ~~ (1,*,*), 'array smartmatch dwims * at end (many *s)');
|
| ok((1,2,3,4) ~~ (*,4), 'array smartmatch dwims * at start');
|
| ok((1,2,3,4) ~~ (*,*,4), 'array smartmatch dwims * at start (many *s)');
|
| ok((1,2,3,4) ~~ (1,*,3,4), 'array smartmatch dwims * 1 elem');
|
| ok((1,2,3,4) ~~ (1,*,*,3,4), 'array smartmatch dwims * 1 elem (many *s)');
|
| ok((1,2,3,4) ~~ (1,*,4), 'array smartmatch dwims * many elems');
|
| ok((1,2,3,4) ~~ (1,*,*,4), 'array smartmatch dwims * many elems (many *s)');
|
| ok((1,2,3,4) ~~ (*,3,*), 'array smartmatch dwims * at start and end');
|
| ok((1,2,3,4) ~~ (*,*,3,*,*), 'array smartmatch dwims * at start and end (many *s)');
|
| ok((1,2,3,4) ~~ (*,1,2,3,4), 'array smartmatch dwims * can match nothing at start');
|
| ok((1,2,3,4) ~~ (*,*,1,2,3,4), 'array smartmatch dwims * can match nothing at start (many *s)');
|
| ok((1,2,3,4) ~~ (1,2,*,3,4), 'array smartmatch dwims * can match nothing in middle');
|
| ok((1,2,3,4) ~~ (1,2,*,*,3,4), 'array smartmatch dwims * can match nothing in middle (many *s)');
|
| ok((1,2,3,4) ~~ (1,2,3,4,*), 'array smartmatch dwims * can match nothing at end');
|
| ok((1,2,3,4) ~~ (1,2,3,4,*,*), 'array smartmatch dwims * can match nothing at end (many *s)');
|
| ok(!((1,2,3,4) ~~ (1,*,3)), '* dwimming does not cause craziness');
|
| ok(!((1,2,3,4) ~~ (*,5)), '* dwimming does not cause craziness');
|
| ok(!((1,2,3,4) ~~ (1,3,*)), '* dwimming does not cause craziness');
|
|
|
| # now try it with arrays as well
|
| my @a = 1, 2, 3;
|
| my @b = 1, 2, 4;
|
| my @m = (*, 2, *); # m as "magic" ;-)
|
|
|
| ok (@a ~~ @a), 'Basic smartmatching on arrays (positive)';
|
| ok (@a !~~ @b), 'Basic smartmatching on arrays (negative)';
|
| ok (@b !~~ @a), 'Basic smartmatching on arrays (negative)';
|
| ok (@a ~~ @m), 'Whatever dwimminess in arrays';
|
| ok (@a ~~ (1, 2, 3)), 'smartmatch Array ~~ List';
|
| ok ((1, 2, 3) ~~ @a), 'smartmatch List ~~ Array';
|
| ok ((1, 2, 3) ~~ @m), 'smartmatch List ~~ Array with dwim';
|
|
|
| ok (1 ~~ *,1,*), 'smartmatch with Array RHS co-erces LHS to list';
|
| ok (1..10 ~~ *,5,*), 'smartmatch with Array RHS co-erces LHS to list';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Set Array array equiv to set $_ === Set(X)
Any Array lists are comparable @$_ «===» X
From t/spec/S03-smartmatch/any-array.t lines 5–24: (skip)
-
| #L<S03/Smart matching/Any Array lists are comparable>
|
| {
|
| class TestArraySmartmatch {
|
| has @!obj;
|
| multi method list() { @!obj };
|
| }
|
|
|
| my $o = TestArraySmartmatch.new(obj => (1, 2, 4));
|
|
|
| ok ($o ~~ [1, 2, 4]), 'Any ~~ Array (basic, +)';
|
| ok !($o ~~ [1, 5, 4]), 'Any ~~ Array (basic, -)';
|
| ok ($o ~~ [1, *]), 'Any ~~ Array (dwim, +)';
|
| ok !($o ~~ [8, *]), 'Any ~~ Array (dwim, -)';
|
| ok (1 ~~ [1]), 'Any ~~ Array (Int, +)';
|
| ok !(1 ~~ [1, 2]), 'Any ~~ Array (Int, -, it is not any())';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Hash Hash hash keys same set $_.keys === X.keys
From t/spec/S03-smartmatch/hash-hash.t lines 7–24: (skip)
-
| #L<S03/Smart matching/Hash Hash hash keys same set>
|
| my %hash1 = ( "foo" => "Bar", "blah" => "ding");
|
| my %hash2 = ( "foo" => "zzz", "blah" => "frbz");
|
| my %hash3 = ( "oink" => "da", "blah" => "zork");
|
| my %hash4 = ( "bink" => "yum", "gorch" => "zorba");
|
| my %hash5 = ( "foo" => 1, "bar" => 1, "gorch" => Mu, "baz" => Mu );
|
|
|
| {
|
| ok (%hash1 ~~ %hash2), 'Hash ~~ Hash (same keys, +)';
|
| ok !(%hash1 ~~ %hash3), 'Hash ~~ Hash (same keys, -)';
|
| #?pugs todo
|
| ok eval_elsewhere('(%hash1 ~~ %hash2)'), "hash keys identical";
|
| ok eval_elsewhere('!(%hash1 ~~ %hash4)'), "hash keys differ";
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Set Hash hash keys same set $_ === X.keys
Array Hash hash slice existence X.{any @$_}:exists
From t/spec/S03-smartmatch/array-hash.t lines 5–20: (skip)
-
| #L<S03/"Smart matching"/Array Hash hash slice existence>
|
| {
|
| my %h = (a => 'b', c => Mu);
|
| ok (['a'] ~~ %h), 'Array ~~ Hash (exists and True)';
|
| ok (['c'] ~~ %h), 'Array ~~ Hash (exists but Mu)';
|
| ok ([<a c>] ~~ %h), 'Array ~~ Hash (both exist)';
|
| ok ([<c d>] ~~ %h), 'Array ~~ Hash (one exists)';
|
| # note that ?any() evaluates to False
|
| ok !( () ~~ %h), 'Array ~~ Hash (empty list)';
|
| ok !(['e'] ~~ %h), 'Array ~~ Hash (not exists)';
|
|
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Regex Hash hash key grep any(X.keys).match($_)
From t/spec/S03-smartmatch/regex-hash.t lines 5–16: (skip)
-
| #L<S03/"Smart matching"/Regex Hash hash key grep>
|
| {
|
| my %h = (moep => 'foo', bar => 'baz');
|
| ok (/oep/ ~~ %h), 'Regex ~~ Hash (+,1)';
|
| ok (/bar/ ~~ %h), 'Regex ~~ Hash (+,2)';
|
| ok !(/ugh/ ~~ %h), 'Regex ~~ Hash (-,1)';
|
| ok !(/foo/ ~~ %h), 'Regex ~~ Hash (-,value)';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Scalar Hash hash entry existence X.{$_}:exists
From t/spec/S03-smartmatch/scalar-hash.t lines 5–15: (skip)
-
| #L<S03/"Smart matching"/Scalar Hash hash entry existence>
|
| {
|
| my %h = (moep => 'foo', bar => Mu);
|
| ok ('moep' ~~ %h), 'Scalar ~~ Hash (+, True)';
|
| ok ('bar' ~~ %h), 'Scalar ~~ Hash (+, False)';
|
| ok !('foo' ~~ %h), 'Scalar ~~ Hash (-)';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Any Hash hash slice existence X.{any @$_}:exists
Str Regex string pattern match .match(X)
Hash Regex hash key "boolean grep" .any.match(X)
Array Regex array "boolean grep" .any.match(X)
Any Regex pattern match .match(X)
Num Range in numeric range X.min <= $_ <= X.max (mod ^'s)
From t/spec/S03-smartmatch/disorganized.t lines 31–152: (skip)
-
| #L<S03/"Smart matching"/in range>
|
| {
|
| # more range tests in t/spec/S03-operators/range.t
|
| #?pugs todo
|
| ok((5 ~~ 1 .. 10), "5 is in 1 .. 10");
|
| ok(!(10 ~~ 1 .. 5), "10 is not in 1 .. 5");
|
| ok(!(1 ~~ 5 .. 10), "1 is not i n 5 .. 10");
|
| ok(!(5 ~~ 5 ^..^ 10), "5 is not in 5 .. 10, exclusive");
|
| };
|
|
|
| # TODO:
|
| # Signature Signature
|
| # Callable Signature
|
| # Capture Signature
|
| # Any Signature
|
|
|
| # Signature Capture
|
|
|
| # reviewed by moritz on 2009-07-07 up to here.
|
|
|
| =begin begin Explanation
|
|
|
| You may be wondering what the heck is with all these try blocks.
|
| Prior to r12503, this test caused a horrible death of Pugs which
|
| magically went away when used inside an eval. So the try blocks
|
| caught that case.
|
|
|
| =end begin Explanation
|
|
|
| {
|
| my $result = 0;
|
| my $parsed = 0;
|
| my @x = 1..20;
|
| try {
|
| $result = all(@x) ~~ { $_ < 21 };
|
| $parsed = 1;
|
| };
|
| ok $parsed, 'C<all(@x) ~~ { ... }> parses';
|
| ok ?$result, 'C<all(@x) ~~ { ... } when true for all';
|
|
|
| $result = 0;
|
| try {
|
| $result = !(all(@x) ~~ { $_ < 20 });
|
| };
|
| ok $result,
|
| 'C<all(@x) ~~ {...} when true for one';
|
|
|
| $result = 0;
|
| try {
|
| $result = !(all(@x) ~~ { $_ < 12 });
|
| };
|
| ok $result,
|
| 'C<all(@x) ~~ {...} when true for most';
|
|
|
| $result = 0;
|
| try {
|
| $result = !(all(@x) ~~ { $_ < 1 });
|
| };
|
| ok $result,
|
| 'C<all(@x) ~~ {...} when true for one';
|
| };
|
|
|
| ok NaN ~~ NaN, 'NaN ~~ NaN is True';
|
|
|
| # need to test in eval() since class defintions happen at compile time,
|
| # ie before the plan is set up.
|
| eval_lives_ok 'class A { method foo { return "" ~~ * } }; A.new.foo',
|
| 'smartmatch in a class lives (RT 62196)';
|
|
|
| # RT #69762
|
| {
|
| ok sub {} ~~ Callable, '~~ Callable (true)';
|
| nok 68762 ~~ Callable, '~~ Callable (false)';
|
| ok 69762 !~~ Callable, '!~~ Callable (true)';
|
| nok sub {} !~~ Callable, '!~~ Callable (false)';
|
|
|
| ok sub {} ~~ Routine, '~~ Routine (true)';
|
| nok 68762 ~~ Routine, '~~ Routine (false)';
|
| ok 69762 !~~ Routine, '!~~ Routine (true)';
|
| nok sub {} !~~ Routine, '!~~ Routine (false)';
|
|
|
| ok sub {} ~~ Sub, '~~ Sub (true)';
|
| nok 68762 ~~ Sub, '~~ Sub (false)';
|
| ok 69762 !~~ Sub, '!~~ Sub (true)';
|
| nok sub {} !~~ Sub, '!~~ Sub (false)';
|
|
|
| ok sub {} ~~ Block, '~~ Block (true)';
|
| nok 68762 ~~ Block, '~~ Block (false)';
|
| ok 69762 !~~ Block, '!~~ Block (true)';
|
| nok sub {} !~~ Block, '!~~ Block (false)';
|
|
|
| ok sub {} ~~ Code, '~~ Code (true)';
|
| nok 68762 ~~ Code, '~~ Code (false)';
|
| ok 69762 !~~ Code, '!~~ Code (true)';
|
| nok sub {} !~~ Code, '!~~ Code (false)';
|
| }
|
| {
|
|
|
| class RT68762 { our method rt68762 {} };
|
|
|
| ok &RT68762::rt68762 ~~ Method, '~~ Method (true)';
|
| nok 68762 ~~ Method, '~~ Method (false)';
|
| ok 69762 !~~ Method, '!~~ Method (true)';
|
| nok &RT68762::rt68762 !~~ Method, '!~~ Method (false)';
|
|
|
| }
|
|
|
| # RT 72048
|
| {
|
| role RT72048_role {}
|
| class RT72048_class does RT72048_role {}
|
|
|
| ok RT72048_class.new ~~ RT72048_role, 'class instance matches role';
|
| nok RT72048_class.new !~~ RT72048_role, 'class instance !!matches role';
|
| }
|
|
|
| ok "foo" ~~ *, 'thing ~~ * is true';
|
| ok ("foo" ~~ *) ~~ Bool, 'thing ~~ * is a boolean';
|
|
|
| done_testing();
|
|
|
| # vim: ft=perl6
|
Str Range in string range X.min le $_ le X.max (mod ^'s)
Any Range in generic range [!after] X.min,$_,X.max (etc.)
Any Type type membership $_.does(X)
From t/spec/S03-smartmatch/any-type.t lines 5–20: (skip)
-
| #L<S03/Smart matching/type membership>
|
| {
|
| class Dog {}
|
| class Cat {}
|
| class Chihuahua is Dog {} # i'm afraid class Pugs will get in the way ;-)
|
| role SomeRole { };
|
| class Something does SomeRole { };
|
|
|
| ok (Chihuahua ~~ Dog), "chihuahua isa dog";
|
| ok (Something ~~ SomeRole), 'something does dog';
|
| ok !(Chihuahua ~~ Cat), "chihuahua is not a cat";
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Signature Signature sig compatibility $_ is a subset of X ???
Callable Signature sig compatibility $_.sig is a subset of X ???
Capture Signature parameters bindable $_ could bind to X (doesn't!)
Any Signature parameters bindable |$_ could bind to X (doesn't!)
Signature Capture parameters bindable X could bind to $_
Any Any scalars are identical $_ === X
From t/spec/S03-smartmatch/any-any.t lines 5–18: (skip)
-
| #L<S03/Smart matching/Any Any scalars are identical>
|
| {
|
| class Smartmatch::ObjTest {}
|
| my $a = Smartmatch::ObjTest.new;
|
| my $b = Smartmatch::ObjTest.new;
|
| ok ($a ~~ $a), 'Any ~~ Any (+)';
|
| ok !($a !~~ $a), 'Any !~~ Any (-)';
|
| ok !($a ~~ $b), 'Any ~~ Any (-)';
|
| ok ($a !~~ $b), 'Any !~~ Any (+)';
|
| }
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
The final rule is applied only if no other pattern type claims X.
All smartmatch types are "itemized"; both ~~ and given/when provide item contexts to their arguments, and autothread any junctive matches so that the eventual dispatch to .ACCEPTS never sees anything "plural". So both $_ and X above are potentially container objects that are treated as scalars. (You may hyperize ~~ explicitly, though. In this case all smartmatching is done using the type-based dispatch to .ACCEPTS, not the form-based dispatch at the front of the table.)
The exact form of the underlying type-based method dispatch is:
X.ACCEPTS($_)
As a single dispatch call this pays attention only to the type of X initially. The ACCEPTS method interface is defined by the Pattern role. Any class composing the Pattern role may choose to provide a single ACCEPTS method to handle everything, which corresponds to those pattern types that have only one entry with an Any on the left above. Or the class may choose to provide multiple ACCEPTS multi-methods within the class, and these will then redispatch within the class based on the type of $_.
The smartmatch table is primarily intended to reflect forms and types that are recognized at compile time. To avoid an explosion of entries, the table assumes the following types will behave similarly:
Actual type Use entries for
=========== ===============
Iterator Seq Array
KeySet KeyBag KeyHash Hash
named values created with
Class, Enum, or Role,
or generic type binding Type
Subst Regex
Char Cat Str
Int UInt etc. Num
Match Capture
Byte Str or Int
Buf Str or Array of Int
(Note, however, that these mappings can be overridden by explicit definition of the appropriate ACCEPTS methods. If the redefinition occurs at compile time prior to analysis of the smart match then the information is also available to the optimizer.)
A Buf type containing any bytes or integers outside the ASCII range may silently promote to a Str type for pattern matching if and only if its relationship to Unicode is clearly declared or typed. This type information might come from an input filehandle, or the Buf role may be a parametric type that allows you to instantiate buffers with various known encodings. In the absence of such typing information, you may still do pattern matching against the buffer, but (apart from assuming the lowest 7 bits represent ASCII) any attempt to treat the buffer as other than a sequence integers is erroneous, and warnings may be generously issued.
Matching against a Grammar treats the grammar as a typename, not as a grammar. You need to use the .parse or .parsefile methods to invoke a grammar.
Matching against a Signature does not actually bind any variables, but only tests to see if the signature could bind. To really bind to a signature, use the * pattern to delegate binding to the when statement's block instead. Matching against * is special in that it takes its truth from whether the subsequent block is bound against the topic, so you can do ordered signature matching:
given $capture {
when * -> Int $a, Str $b { ... }
when * -> Str $a, Int $b { ... }
when * -> $a, $b { ... }
when * { ... }
}
This can be useful when the unordered semantics of multiple dispatch are insufficient for defining the "pecking order" of code. Note that you can bind to either a bare block or a pointy block. Binding to a bare block conveniently leaves the topic in $_, so the final form above is equivalent to a default. (Placeholder parameters may also be used in the bare block form, though of course their types cannot be specified that way.)
There is no pattern matching defined for the Any pattern, so if you find yourself in the situation of wanting a reversed smartmatch test with an Any on the right, you can almost always get it by explicit call to the underlying ACCEPTS method using $_ as the pattern. For example:
$_ X Type of Match Wanted What to use on the right
====== === ==================== ========================
Callable Any item sub truth .ACCEPTS(X) or .(X)
Range Any in range .ACCEPTS(X)
Type Any type membership .ACCEPTS(X) or .does(X)
Regex Any pattern match .ACCEPTS(X)
etc.
Similar tricks will allow you to bend the default matching rules for composite objects as long as you start with a dotted method on $_:
given $somethingordered {
when .values.'[<=]' { say "increasing" }
when .values.'[>=]' { say "decreasing" }
}
In a pinch you can define a macro to do the "reversed when":
my macro statement_control:<ACCEPTS> () { "when .ACCEPTS: " }
given $pattern {
ACCEPTS $a { ... }
ACCEPTS $b { ... }
ACCEPTS $c { ... }
}
Various proposed-but-deprecated smartmatch behaviors may be easily (and we hope, more readably) emulated as follows:
$_ X Type of Match Wanted What to use on the right
====== === ==================== ========================
Array Num array element truth .[X]
Array Num array contains number *,X,*
Array Str array contains string *,X,*
Array Seq array begins with seq X,*
Array Seq array contains seq *,X,*
Array Seq array ends with seq *,X
Hash Str hash element truth .{X}
Hash Str hash key existence .{X}:exists
Hash Num hash element truth .{X}
Hash Num hash key existence .{X}:exists
Buf Int buffer contains int .match(X)
Str Char string contains char .match(X)
Str Str string contains string .match(X)
Array Scalar array contains item .any === X
Str Array array contains string X.any
Num Array array contains number X.any
Scalar Array array contains object X.any
Hash Array hash slice exists .{X.all}:exists .{X.any}:exists
Set Set subset relation .{X}:exists
Set Hash subset relation .{X}:exists
Any Set subset relation .Set.{X}:exists
Any Hash subset relation .Set.{X}:exists
Any Set superset relation X.{$_}:exists
Any Hash superset relation X.{$_}:exists
Any Set sets intersect .{X.any}:exists
Set Array subset relation X,* # (conjectured)
Array Regex match array as string .Cat.match(X) cat(@$_).match(X)
(Note that the .cat method and the Cat type coercion both take a single object, unlike the cat function which, as a list operator, takes a syntactic list (or multilist) and flattens it. All of these return a Cat object, however.)
Boolean expressions are those known to return a boolean value, such as comparisons, or the unary ? operator. They may reference $_ explicitly or implicitly. If they don't reference $_ at all, that's okay too--in that case you're just using the switch structure as a more readable alternative to a string of elsifs. Note, however, that this means you can't write:
given $boolean {
when True {...}
when False {...}
}
because it will always choose the True case. Instead use something like a conditional context uses internally:
given $boolean {
when .Bool == 1 {...}
when .Bool == 0 {...}
}
Better, just use an if statement. In any case, if you try to smartmatch with ~~ or when, it will recognize True or False syntactically and warn you that it won't do what you expect. The compiler is also allowed to warn about any other boolean construct that does not test $_, to the extent it can detect that.
In a similar vein, any function (such as grep) that takes a Matcher will not accept an argument of type Bool, since that almost always indicates a programming error. (One may always use * to match anything, if that's what you really want. Or use a closure that returns a constant boolean value.)
Note also that regex matching does not return a Bool, but merely a Match object that can be used as a boolean value. Use an explicit ? or true to force a Bool value if desired.
The primary use of the ~~ operator is to return a boolean value in a boolean context. However, for certain operands such as regular expressions, use of the operator within item or list context transfers the context to that operand, so that, for instance, a regular expression can return a list of matched substrings, as in Perl 5. This is done by returning an object that can return a list in list context, or that can return a boolean in a boolean context. In the case regex matching the Match object is a kind of Capture, which has these capabilities.
For the purpose of smartmatching, all Set and Bag values are considered to be of type KeyHash, that is, Hash containers where the keys represent the unique objects and the values represent the replication count of those unique keys. (Obviously, a Set can have only 0 or 1 replication because of the guarantee of uniqueness).
The Cat type allows you to have an infinitely extensible string. You can match an array or iterator by feeding it to a Cat, which is essentially a Str interface over an iterator of some sort. Then a Regex can be used against it as if it were an ordinary string. The Regex engine can ask the string if it has more characters, and the string will extend itself if possible from its underlying iterator. (Note that such strings have an indefinite number of characters, so if you use .* in your pattern, or if you ask the string how many characters it has in it, or if you even print the whole string, it may be feel compelled to slurp in the rest of the string, which may or may not be expeditious.)
The cat operator takes a (potentially lazy) list and returns a Cat object. In string context this coerces each of its elements to strings lazily, and behaves as a string of indeterminate length. You can search a gather like this:
my $lazystr := cat gather for @foo { take .bar }
$lazystr ~~ /pattern/;
The Cat interface allows the regex to match element boundaries with the <,> assertion, and the StrPos objects returned by the match can be broken down into elements index and position within that list element. If the underlying data structure is a mutable array, changes to the array (such as by shift or pop) are tracked by the Cat so that the element numbers remain correct. Strings, arrays, lists, sequences, captures, and tree nodes can all be pattern matched by regexes or by signatures more or less interchangeably.
An appended : marks the invocant when using the indirect-object syntax for Perl 6 method calls. The following two statements are equivalent:
$hacker.feed('Pizza and cola');
feed $hacker: 'Pizza and cola';
A colon may also be used on an ordinary method call to indicate that it should be parsed as a list operator:
$hacker.feed: 'Pizza and cola';
This colon is a separate token. A colon prefixing an adverb is not a separate token. Therefore, under the longest-token rule,
$hacker.feed:xxx('Pizza and cola');
is tokenized as an adverb applying to the method as its "toplevel preceding operator":
$hacker.feed :xxx('Pizza and cola');
not as an xxx sub in the argument list of .feed:
$hacker.feed: xxx('Pizza and cola'); # wrong
If you want both meanings of colon in order to supply both an adverb and some positional arguments, you have to put the colon twice:
$hacker.feed: :xxx('Pizza and cola'), 1,2,3;
(For similar reasons it's required to put whitespace after the colon of a label.)
Note in particular that because of adverbial precedence:
1 + $hacker.feed :xxx('Pizza and cola');
will apply the :xxx adverb to the + operator, not the method call. This is not likely to succeed.
From t/spec/S03-operators/adverbial-modifiers.t lines 7–303: (skip)
-
| # L<S03/Invocant marker/"will apply the :xxx adverb">
|
| sub prefix:<blub> (Str $foo, Int :$times = 1) {
|
| ("BLUB" x $times) ~ $foo;
|
| }
|
|
|
| is prefix:<blub>("bar"), 'BLUBbar', 'user-defined prefix operator, long name';
|
| is prefix:<blub>("bar", times => 2), 'BLUBBLUBbar', 'user-defined prefix operator, long name, optional parameter';
|
| is prefix:<blub>(:times(2), "bar"), 'BLUBBLUBbar', 'user-defined prefix operator, long name, :times adverb, leading';
|
| is prefix:<blub>("bar", :times(2)), 'BLUBBLUBbar', 'user-defined prefix operator, long name, :times adverb, trailing';
|
| is blub "bar", 'BLUBbar', 'user-defined prefix operator, basic call';
|
| is blub "bar" :times(2), 'BLUBBLUBbar', 'user-defined prefix operator, :times adverb, space';
|
| is blub "bar":times(2), 'BLUBBLUBbar', 'user-defined prefix operator, :times adverb, no space';
|
|
|
| {
|
| # These basic adverb tests are copied from a table in A12.
|
| my $bar = 123;
|
| my @many = (4,5);
|
| sub dostuff(){"stuff"}
|
| my ($v,$e);
|
| $e = (foo => $bar);
|
| $v = :foo($bar);
|
| is ~$v, ~$e, ':foo($bar)';
|
|
|
| $e = (foo => [1,2,3,@many]);
|
| $v = :foo[1,2,3,@many];
|
| is ~$v, ~$e, ':foo[1,2,3,@many]';
|
|
|
| $e = (foo => «alice bob charles»);
|
| $v = :foo«alice bob charles»;
|
| is ~$v, ~$e, ':foo«alice bob charles»';
|
|
|
| $e = (foo => 'alice');
|
| $v = :foo«alice»;
|
| is ~$v, ~$e, ':foo«alice»';
|
|
|
| $e = (foo => { a => 1, b => 2 });
|
| $v = eval ':foo{ a => 1, b => 2 }';
|
| is ~$v, ~$e, ':foo{ a => 1, b => 2 }';
|
|
|
| $e = (foo => { dostuff() });
|
| $v = :foo{ dostuff() };
|
| is ~$v, ~$e, ':foo{ dostuff() }';
|
|
|
| $e = (foo => 0);
|
| $v = :foo(0);
|
| is ~$v, ~$e, ':foo(0)';
|
|
|
| $e = (foo => 1);
|
| $v = :foo;
|
| is ~$v, ~$e, ':foo';
|
| }
|
|
|
| {
|
| # Exercise various mixes of "f", parens "()",
|
| # and adverbs with "X' and without "x" an argument.
|
|
|
| my sub f(:$x,:$y){$x~$y}
|
| my $v;
|
|
|
| # f(XY) f(YX) f(xY) f(Xy)
|
|
|
| $v = f(:x("a"):y("b"));
|
| is $v, "ab", 'f(:x("a"):y("b"))';
|
|
|
| $v = f(:y("b"):x("a"));
|
| is $v, "ab", 'f(:y("b"):x("a"))';
|
|
|
| $v = f(:x:y("b"));
|
| is $v, "1b", 'f(:x:y("b"))';
|
|
|
| $v = f(:x("a"):y);
|
| is $v, "a1", 'f(:x("a"):y)';
|
|
|
| # fXY() fxY() fXy()
|
|
|
| # $v = f:x("a"):y("b")();
|
| # is $v, "ab", 'f:x("a"):y("b")()';
|
|
|
| # $v = f:x:y("b")();
|
| # is $v, "1b", 'f:x:y("b")()';
|
|
|
| # $v = f:x("a"):y ();
|
| # is $v, "a1", 'f:x("a"):y ()';
|
|
|
| # fX(Y) fY(X) fx(Y) fX(y)
|
|
|
| # $v = f:x("a")(:y("b"));
|
| # is $v, "ab", 'f:x("a")(:y("b"))';
|
|
|
| # $v = f:y("b")(:x("a"));
|
| # is $v, "ab", 'f:y("b")(:x("a"))';
|
|
|
| # $v = f:x (:y("b"));
|
| # is $v, "1b", 'f:x (:y("b"))';
|
|
|
| # $v = f:x("a")(:y);
|
| # is $v, "a1", 'f:x("a")(:y)';
|
|
|
| # fXY fxY fXy
|
|
|
| $v = f:x("a"):y("b");
|
| is $v, "ab", 'f:x("a"):y("b")';
|
|
|
| $v = f:x:y("b");
|
| is $v, "1b", 'f:x:y("b")';
|
|
|
| $v = f:x("a"):y;
|
| is $v, "a1", 'f:x("a"):y';
|
|
|
| # f(X)Y f(Y)X f(x)Y f(X)y f(x)y
|
|
|
| $v = f(:x("a")):y("b");
|
| is $v, "ab", 'f(:x("a")):y("b")';
|
|
|
| $v = f(:y("b")):x("a");
|
| is $v, "ab", 'f(:y("b")):x("a")';
|
|
|
| $v = f(:x):y("b");
|
| is $v, "1b", 'f(:x("a")):y("b")';
|
|
|
| $v = f(:x("a")):y;
|
| is $v, "a1", 'f(:x("a")):y';
|
|
|
| $v = f(:x):y;
|
| is $v, "11", 'f(:x):y';
|
|
|
| # f()XY f()YX f()xY f()Xy f()xy
|
|
|
| $v = f():x("a"):y("b");
|
| is $v, "ab", 'f():x("a"):y("b")';
|
|
|
| $v = f():y("b"):x("a");
|
| is $v, "ab", 'f():y("b"):x("a")';
|
|
|
| $v = f():x:y("b");
|
| is $v, "1b", 'f():x:y("b")';
|
|
|
| $v = f():x("a"):y;
|
| is $v, "a1", 'f():x("a"):y';
|
|
|
| $v = f():x:y;
|
| is $v, "11", 'f():x:y';
|
|
|
| # fX()Y fY()X fx()y
|
|
|
| # $v = f:x("a")():y("b");
|
| # is $v, "ab", 'f:x("a")():y("b")';
|
|
|
| # $v = f:y("b")():x("a");
|
| # is $v, "ab", 'f:y("b")():x("a")';
|
|
|
| # $v = f:x ():y;
|
| # is $v, "11", 'f:x ():y';
|
|
|
| # f_X(Y) f_X_Y() f_X_Y_() f_XY_() f_XY() fXY ()
|
|
|
| # $v = f :x("a")(:y("b"));
|
| # is $v, "ab", 'f :x("a")(:y("b"))';
|
| # Since the demagicalizing of pairs, this test shouldn't and doesn't work any
|
| # longer.
|
|
|
| # $v = 'eval failed';
|
| # eval '$v = f :x("a") :y("b")()';
|
| # #?pugs todo 'bug'
|
| # is $v, "ab", 'f :x("a") :y("b")()';
|
|
|
| # $v = 'eval failed';
|
| # eval '$v = f :x("a") :y("b") ()';
|
| # #?pugs todo 'bug'
|
| # is $v, "ab", 'f :x("a") :y("b") ()';
|
|
|
| # $v = 'eval failed';
|
| # eval '$v = f :x("a"):y("b") ()';
|
| # #?pugs todo 'bug'
|
| # is $v, "ab", 'f :x("a"):y("b") ()';
|
|
|
| # $v = 'eval failed';
|
| # eval '$v = f :x("a"):y("b")()';
|
| # #?pugs todo 'bug'
|
| # is $v, "ab", 'f :x("a"):y("b")()';
|
|
|
| # $v = f:x("a"):y("b") ();
|
| # is $v, "ab", 'f:x("a"):y("b") ()';
|
|
|
| #
|
|
|
| # more tests....
|
|
|
| }
|
|
|
| {
|
| # Exercise mixes of adverbs and positional arguments.
|
|
|
| my $v;
|
| my sub f($s,:$x) {$x~$s}
|
| my sub g($s1,$s2,:$x) {$s1~$x~$s2}
|
| my sub h(*@a) {@a.perl}
|
| my sub i(*%h) {%h.perl}
|
| my sub j($s1,$s2,*%h) {$s1~%h.perl~$s2}
|
|
|
| # f(X s) f(Xs) f(s X) f(sX) f(xs) f(sx)
|
|
|
| $v = f(:x("a"), "b");
|
| is $v, "ab", 'f(:x("a") "b")';
|
|
|
| $v = f(:x("a"),"b");
|
| is $v, "ab", 'f(:x("a")"b")';
|
|
|
| $v = f("b", :x("a"));
|
| is $v, "ab", 'f("b" :x("a"))';
|
|
|
| $v = f("b",:x("a"));
|
| is $v, "ab", 'f("b":x("a"))';
|
|
|
| $v = f(:x, "b");
|
| is $v, "1b", 'f(:x "b")';
|
|
|
| $v = f("b", :x);
|
| is $v, "1b", 'f("b" :x)';
|
|
|
| # fX(s) f(s)X
|
|
|
| # $v = f:x("a")("b");
|
| # is $v, "ab", 'f:x("a")("b")';
|
|
|
| $v = f("b"):x("a");
|
| is $v, "ab", 'f("b"):x("a")';
|
|
|
| # fX s fXs fx s
|
|
|
| $v = 'eval failed';
|
| eval '$v = f:x("a") "b"';
|
| is $v, "ab", 'f:x("a") "b"';
|
|
|
| $v = 'eval failed';
|
| eval '$v = f:x("a")"b"';
|
| is $v, "ab", 'f:x("a")"b"';
|
|
|
| $v = 'eval failed';
|
| eval '$v = f:x "b"';
|
| is $v, "1b", 'f:x "b"';
|
|
|
| # fs X fsX fs x fsx
|
|
|
| # $v = f "b" :x("a");
|
| # is $v, "ab", 'f "b" :x("a")';
|
|
|
| # $v = f "b":x("a");
|
| # is $v, "ab", 'f "b":x("a")';
|
|
|
| # $v = f "b" :x;
|
| # is $v, "1b", 'f "b" :x';
|
|
|
| # $v = f "b":x;
|
| # is $v, "1b", 'f "b":x';
|
|
|
| { # adverbs as pairs
|
|
|
| my sub f1($s,:$x){$s.perl~$x}
|
|
|
| $v = f1(\:bar :x("b"));
|
| is $v, '("bar" => Bool::True)b', 'f1(\:bar :x("b"))';
|
|
|
| my sub f2(Pair $p){$p.perl}
|
|
|
| $v = f2((:bar));
|
| is $v, '("bar" => Bool::True)', 'f2((:bar))';
|
|
|
| my sub f3(Pair $p1, Pair $p2){$p1.perl~" - "~$p2.perl}
|
|
|
| $v = f3((:bar),(:hee(3)));
|
| is $v, '("bar" => Bool::True) - ("hee" => 3)', 'f3((:bar),(:hee(3)))';
|
|
|
| }
|
|
|
| # add more tests...
|
|
|
| }
|
|
|
| {
|
| # Exercise adverbs on operators.
|
|
|
| sub prefix:<zpre>($a,:$x){join(",",$a,$x)}
|
|
|
| is (zpre 4 :x(5)), '4,5', '(zpre 4 :x(5))';
|
|
|
| sub postfix:<zpost>($a,:$x){join(",",$a,$x)}
|
|
|
| is (4zpost :x(5)), '4,5', '(4 zpost :x(5))';
|
|
|
| sub infix:<zin>($a,$b,:$x){join(",",$a,$b,$x)}
|
|
|
| is (3 zin 4 :x(5)), '3,4,5', '(3 zin 4 :x(5))';
|
|
|
| }
|
|
|
| # vim: ft=perl6
|
From t/spec/S03-operators/feed.t lines 6–137: (skip)
-
| # L<S03/"Feed operators">
|
|
|
| =begin pod
|
|
|
| Tests for the feed operators
|
|
|
| ==> and <==
|
|
|
| =end pod
|
|
|
| plan 23;
|
|
|
| #?pugs skip '<== dies with cast error'
|
| {
|
| my @a = (1, 2);
|
| my (@b, @c);
|
|
|
| @a ==> @b;
|
| @c <== @a;
|
|
|
| #?pugs 2 todo 'feed operators do not work'
|
| is(~@b, ~@a, "ltr feed as simple assignment");
|
| is(~@c, ~@a, "rtl feed as simple assignment");
|
| }
|
|
|
| #?pugs skip '<== dies with cast error'
|
| {
|
| my @a = (1 .. 5);
|
| my @e = (2, 4);
|
|
|
| my (@b, @c);
|
| @a ==> grep { ($_ % 2) == 0 } ==> @b;
|
| @c <== grep { ($_ % 2) == 0 } <== @a;
|
| my @f = do {@a ==> grep {($_ % 2) == 0}};
|
| my @g = (@a ==> grep {($_ % 2) == 0});
|
|
|
| #?pugs 4 todo 'feed operators do not work'
|
| is(~@b, ~@e, "array ==> grep ==> result");
|
| is(~@c, ~@e, "result <== grep <== array");
|
| is(~@f, ~@e, 'result = do {array ==> grep}');
|
| is(~@g, ~@e, 'result = (array ==> grep)');
|
| }
|
|
|
| {
|
| my ($got_x, $got_y, @got_z);
|
| sub foo ($x, $y?, *@z) {
|
| $got_x = $x;
|
| $got_y = $y;
|
| @got_z = @z;
|
| }
|
|
|
| my @a = (1 .. 5);
|
|
|
| @a ==> foo "x";
|
|
|
| #?pugs todo 'feed operators do not work'
|
| is($got_x, "x", "x was passed as explicit param");
|
| ok(!defined($got_y), "optional param y was not bound to fed list");
|
| #?pugs todo 'feed operators do not work'
|
| is(~@got_z, ~@a, '...slurpy array *@z got it');
|
| }
|
|
|
| {
|
| my @data = <1 2 4 5 7 8>;
|
| my @odds = <1 5 7>;
|
|
|
| eval_dies_ok('@data <== grep {$_ % 2} <== @data', 'a chain of feeds may not begin and end with the same array');
|
|
|
| @data = <1 2 4 5 7 8>;
|
| @data <== grep {$_ % 2} <== eager @data;
|
| is(~@data, ~@odds, '@arr <== grep <== eager @arr works');
|
|
|
| @data = <1 2 4 5 7 8>;
|
| @data <== eager grep {$_ % 2} <== @data;
|
| is(~@data, ~@odds, '@arr <== eager grep <== @arr works');
|
| }
|
|
|
| # checking the contents of a feed: installing a tap
|
| {
|
| my @data = <0 1 2 3 4 5 6 7 8 9>;
|
| my @tap;
|
|
|
| @data <== map {$_ + 1} <== @tap <== grep {$_ % 2} <== eager @data;
|
| is(@tap, <1 3 5 7 9>, '@tap contained what was expected at the time');
|
| is(@data, <2 4 6 8 10>, 'final result was unaffected by the tap variable');
|
| }
|
|
|
| # no need for temp variables in feeds: $(*), @(*), %(*)
|
| {
|
| my @data = 'a' .. 'z';
|
| my @out = <a e i o u y>;
|
|
|
| @data ==> grep {/<[aeiouy]>/} ==> is($(*), $(@out), 'basic test for $(*)');
|
| @data ==> grep {/<[aeiouy]>/} ==> is(@(*), @(@out), 'basic test for @(*)');
|
| @data ==> grep {/<[aeiouy]>/} ==> is(%(*), %(@out), 'basic test for %(*)');
|
|
|
| # XXX: currently the same as the @(*) test above. Needs to be improved
|
| @data ==> grep {/<[aeiouy]>/} ==> is(@(*).slice, @(@out).slice, 'basic test for @(*).slice');
|
| }
|
|
|
| # <<== and ==>> pretending to be unshift and push, respectively
|
| {
|
| my @odds = <1 3 5 7 9>;
|
| my @even = <0 2 4 6 8>;
|
|
|
| my @numbers = do {@odds ==>> @even};
|
| is(~@numbers, ~(@even, @odds), 'basic ==>> test');
|
|
|
| @numbers = do {@odds <<== @even};
|
| is(~@numbers, ~(@odds, @even), 'basic <<== test');
|
| }
|
|
|
| # feeding to whatever using ==> and ==>>
|
|
|
| {
|
| my @data = 'a' .. 'e';
|
|
|
| @data ==> *;
|
| is(@(*), @data, 'basic feed to whatever');
|
|
|
| <a b c d> ==> *;
|
| 0 .. 3 ==>> *;
|
| is(@(*), <a b c d 0 1 2 3>, 'two feeds to whatever as array');
|
| }
|
|
|
| # stacked feeds
|
| {
|
| ('a' .. 'd'; 0 .. 3) ==> my @data;
|
| is(@(@data), <a b c d 0 1 2 3>, 'two stacked feeds');
|
| }
|
|
|
| # vim: ft=perl6
|
The new operators ==> and <== are akin to UNIX pipes, but work with functions or statements that accept and return lists. Since these lists are composed of discrete objects and not liquids, we call these feed operators rather than pipes. For example,
@result = map { floor($^x / 2) },
grep { /^ \d+ $/ },
@data;
can also now be written with rightward feeds as:
@data ==> grep { /^ \d+ $/ }
==> map { floor($^x / 2) }
==> @result;
or with leftward feeds as:
@result <== map { floor($^x / 2) }
<== grep { /^ \d+ $/ }
<== @data;
Either form more clearly indicates the flow of data. See S06 for more of the (less-than-obvious) details on these two operators.
Perl 6's operators have been greatly regularized, for instance, by consistently prefixing numeric, stringwise, and boolean operators with +, ~ and ? respectively to indicate whether the bitwise operation is done on a number, a string, or a single bit. But that's just a naming convention, and if you wanted to add a new bitwise ¬ operator, you'd have to add the +¬, ~¬, and ?¬ operators yourself. Similarly, the carets that exclude the endpoints on ranges are there by convention only.
In contrast to that, Perl 6 has eight standard metaoperators for turning a given existing operator into a related operator that is more powerful (or at least differently powerful). These differ from a mere naming convention in that Perl automatically generates these new operators from user-defined operators as well as from builtins. In fact, you're not generally supposed to define the individual metaoperations--their semantics are supposed to be self-evident by the transformation of the base operator. In other words, these metaoperators are really just shorthand for higher-order functions (functions that take other functions as arguments).
Constructs containing metaoperators are considered "metatokens", by which we mean that they are not subject to ordinary longest-token matching rules, although their components are. Like ordinary tokens, however, metatokens do not allow whitespace between their subparts.
From t/spec/S03-operators/autovivification.t lines 4–100: (skip)
-
| # L<S03/Assignment operators/>
|
|
|
| {
|
| my $x;
|
| $x++;
|
| ok $x == 1, 'my $x; $x++ works'
|
| }
|
|
|
| {
|
| my Int $x;
|
| $x++;
|
| ok $x == 1, 'my Int $x; $x++ works'
|
| }
|
|
|
| {
|
| my $x;
|
| $x += 1;
|
| ok $x == 1, 'my $x; $x += 1 works'
|
| }
|
|
|
| {
|
| my Int $x;
|
| $x += 1;
|
| ok $x == 1, 'my Int $x; $x += 1 works'
|
| }
|
|
|
| {
|
| my $x;
|
| $x -= 1;
|
| ok $x == -1, 'my $x; $x -= 1 works'
|
| }
|
|
|
| {
|
| my Int $x;
|
| $x -= 1;
|
| ok $x == -1, 'my Int $x; $x -= 1 works'
|
| }
|
|
|
| {
|
| my $s;
|
| $s ~= 'ab';
|
| is $s, 'ab', 'my $s; $s ~= "ab" works'
|
| }
|
|
|
| {
|
| my Str $s;
|
| $s ~= 'ab';
|
| is $s, 'ab', 'my Str $s; $s ~= "ab" works'
|
| }
|
|
|
| {
|
| my $x;
|
| $x *= 2;
|
| ok $x == 2, 'my $x; $x *= 2 works'
|
| }
|
|
|
| {
|
| my $x;
|
| $x **= 2;
|
| ok $x == 1, 'my $x; $x **= 2 works'
|
| }
|
|
|
| {
|
| my Int $x;
|
| $x *= 2;
|
| ok $x == 2, 'my Int $x; $x *= 2 works'
|
| }
|
|
|
| {
|
| my Int $x;
|
| $x **= 2;
|
| ok $x == 1, 'my Int $x; $x **= 2 works'
|
| }
|
|
|
| {
|
| my $x;
|
| $x = $x + 1i;
|
| is_approx($x, 0 + 1i, 'my $x; $x = $x + 1i; works');
|
| }
|
|
|
| {
|
| my $x;
|
| $x += 1i;
|
| is_approx($x, 0 + 1i, 'my $x; $x += 1i; works');
|
| }
|
|
|
| {
|
| my $i **= $i;
|
| is $i, 1, 'my $i **= $i';
|
| }
|
|
|
| {
|
| my $x;
|
| $x *= 1i;
|
| is_approx($x, 1i, 'my $x; $x *= 1i works');
|
| }
|
|
|
Assignment operators are already familiar to C and Perl programmers. (Though the .= operator now means to call a mutating method on the object on the left, and ~= is string concatenation.) Most non-relational infix operators may be turned into their corresponding assignment operator by suffixing with =. The limitation is actually based on whether the left side can function both as an rvalue and an lvalue by the usual correspondence:
A op= B;
From t/spec/S03-operators/inplace.t lines 5–49: (skip)
-
| # L<S03/Assignment operators/A op= B>
|
|
|
| plan 22;
|
|
|
| {
|
| my @a = (1, 2, 3);
|
| lives_ok({@a .= map: { $_ + 1 }}, '.= runs with block');
|
| is(@a[0], 2, 'inplace map [0]');
|
| is(@a[1], 3, 'inplace map [1]');
|
| is(@a[2], 4, 'inplace map [2]');
|
| }
|
|
|
| {
|
| my @b = <foo 123 bar 456 baz>;
|
| lives_ok { @b.=grep({/<[a..z]>/})},
|
| '.= works without surrounding whitespace';
|
| is @b[0], 'foo', 'inplace grep [0]';
|
| is @b[1], 'bar', 'inplace grep [1]';
|
| is @b[2], 'baz', 'inplace grep [2]';
|
| }
|
|
|
| {
|
| my $a=3.14;
|
| $a .= Int;
|
| is($a, 3, "inplace int");
|
|
|
| my $b = "a_string"; $b .= WHAT;
|
| my $c = 42; $c .= WHAT;
|
| my $d = 42.23; $d .= WHAT;
|
| my @e = <a b c d>; @e .= WHAT;
|
| is($b, Str, "inplace WHAT of a Str");
|
| is($c, Int, "inplace WHAT of a Num");
|
| is($d, Rat, "inplace WHAT of a Rat");
|
| is(@e[0], Array, "inplace WHAT of an Array");
|
| }
|
|
|
| my $f = "lowercase"; $f .= uc;
|
| my $g = "UPPERCASE"; $g .= lc;
|
| my $h = "lowercase"; $h .= ucfirst;
|
| my $i = "UPPERCASE"; $i .= lcfirst;
|
| is($f, "LOWERCASE", "inplace uc");
|
| is($g, "uppercase", "inplace lc");
|
| is($h, "Lowercase", "inplace ucfist");
|
| is($i, "uPPERCASE", "inplace lcfirst");
|
|
|
A = A op B;
Existing forms ending in = may not be modified with this metaoperator.
Regardless of the precedence of the base operator, the precedence of any assignment operator is forced to be the same as that of ordinary assignment. If the base operator is tighter than comma, the expression is parsed as item assignment. If the base operator is the same or looser than comma, the expression is parsed as a list assignment:
$a += 1, $b += 2 # two separate item assignments
@foo ,= 1,2,3 # same as push(@foo,1,2,3)
From t/spec/S03-operators/assign.t lines 699–762: (skip)
-
| # L<S03/Assignment operators/",=">
|
| #?rakudo skip ',='
|
| {
|
| my @a = 1, 2;
|
| is (@a ,= 3, 4).join('|'), '1|2|3|4', ',= on lists works the same as push (return value)';
|
| is @a.join('|'), '1|2|3|4', ',= on lists works the same as push (effect on array)';
|
| }
|
|
|
| # RT #63642
|
| #?rakudo skip 'no applicable methods (,=)'
|
| {
|
| my %part1 = a => 'b';
|
| my %part2 = d => 'c';
|
| my %both = %part1, %part2;
|
|
|
| my %retval = ( %part1 ,= %part2 );
|
|
|
| ok %retval eqv %both, ',= works for hashes (return value)';
|
| ok %part1 eqv %both, ',= works for hashes (hash modified)';
|
| }
|
|
|
| {
|
| my $s = 'abc';
|
| $s .= subst('b','d');
|
| is $s, 'adc', '.= on scalars works';
|
|
|
| my @a = 'abc';
|
| @a[0] .= subst('b','d');
|
| is @a[0], 'adc', '.= on array indexed values';
|
|
|
| my %h = x => 'abc';
|
| %h<x> .= subst('b','d');
|
| is %h<x>, 'adc', '.= on hash keyed values';
|
| }
|
|
|
| {
|
| my $x = 3;
|
| $x min= 2;
|
| is $x, 2, 'min= worked (positive)';
|
|
|
| $x = 3;
|
| $x min= 5;
|
| is $x, 3, 'min= worked (negative)';
|
|
|
| $x = 1;
|
| $x max= 2;
|
| is $x, 2, 'max= worked (positive)';
|
|
|
| $x = 3;
|
| $x max= 2;
|
| is $x, 3, 'max= worked (negative)';
|
| }
|
|
|
| # from ye auld t/syntax/decl_vs_assign_prec.t
|
| {
|
| is((try {my $t; $t = (1 == 1) ?? "true" !! "false"; $t}), "true", 'my $t; $t = (cond) ?? !! gets value from ?? !!, not conds bool');
|
| is((try {my $t; $t = (1 == 0) ?? "true" !! "false"; $t}), "false", '.. also for false');
|
| is((try {our $t; $t = (1 == 1) ?? "true" !! "false"; $t}), "true", 'truth with "our"');
|
| is((try {our $t; $t = (1 == 0) ?? "true" !! "false"; $t}), "false", '... and false');
|
| is((try {my $t = (1 == 1) ?? "true" !! "false"; $t}), "true", 'my $t = (cond) ?? !! gets value from ?? !!');
|
| is((try {my $t = (1 == 0) ?? "true" !! "false"; $t}), "false", '.. also for false');
|
| }
|
|
|
| # vim: ft=perl6
|
@foo Z= 1,2,3 # same as @foo = @foo Z 1,2,3
Note that metaassignment to a list does not automatically distribute the right argument over the assigned list unless the base operator does (as in the Z case above). Hence if you want to say:
($a,$b,$c) += 1; # ILLEGAL
you must instead use a hyperoperator (see below):
($a,$b,$c) »+=» 1; # add one to each of three variables
If you apply an assignment operator to a container containing a type object (which is undefined), it is assumed that you are implementing some kind of notional "reduction" to an accumulator variable. To that end, the operation is defined in terms of the corresponding reduction operator, where the type object autovivifies to the operator's identify value. So if you say:
From t/spec/S03-operators/autovivification.t lines 101–141: (skip)
-
| # L<S03/Assignment operators/"If you apply an assignment operator to a
|
| # container">
|
| {
|
| # yes, this is serious. It's in the specs ;-)
|
| my Num $x;
|
| $x *= 5;
|
| is $x, 5, '*= autovivifies with correct neutral element (with Num proto)';
|
| }
|
|
|
| {
|
| my $x;
|
| $x *= 5;
|
| is $x, 5, '*= autovivifies with correct neutral element (without type constraint)';
|
| }
|
|
|
| #?rakudo skip 'Hash element autoviv'
|
| {
|
| my Int %h;
|
| is (%h<foo> *= 23), 23, '*= autovivifies with correct neutral element (with Int proto on hash items)';
|
| }
|
|
|
| {
|
| my %h;
|
| is (%h<foo> *= 23), 23, '*= autovivifies with correct neutral element (without proto on hash items)';
|
| }
|
|
|
| {
|
| my @empty;
|
| is +@empty, 0, 'Sanity: empty array, @empty, has 0 elements';
|
|
|
| my $before = @empty.perl;
|
| @empty[5] ~~ /nothing/;
|
| my $after = @empty.perl;
|
|
|
| #?pugs 2 todo 'bugs'
|
| is +@empty,0,'empty array, @empty, has 0 elements';
|
|
|
| is $after,$before,"Array elements are not auto-vivified by smartmatch";
|
| }
|
|
|
| # vim: ft=perl6
|
$x -= 1;
it is more or less equivalent to:
$x = [-]() unless defined $x; # 0 for [-]()
$x = $x - 1;
and $x ends up with -1 in it, as expected.
Hence you may correctly write:
my Num $prod;
for @factors -> $f {
$prod *= $f;
}
While this may seem marginally useful in the scalar variable case, it's much more important for it to work this way when the modified location may have only just been created by autovivification. In other words, if you write:
%prod{$key} *= $f
you need not worry about whether the hash element exists yet. If it does not, it will simply be initialized with the value of $f.
From t/spec/S03-operators/equality.t lines 43–60: (skip)
-
| #L<S03/Negated relational operators>
|
| ok(2 !== 3, "!== true");
|
| ok(!(2 !== 2), "!== false");
|
| ok($foo !eq "f", "!eq true undef");
|
| ok("" !eq "f", "!eq true empty string");
|
| #?rakudo todo "undef not coerced properly"
|
| ok(!($foo !eq ""), "!eq false undef and empty string");
|
| ok(!($foo !eq $foo), "!eq false undef twice");
|
| ok(!("" !eq ""), "!eq false empty string twice");
|
| ok(!("xc" !eq "xc"), "!eq false non-empty string twice");
|
|
|
| # numeric context on undefined values
|
| ok($foo == 0, "Any == 0");
|
| ok(@foo[0] == 0, "Array Any == 0");
|
|
|
| # XXX: need tests for coercion string and numeric coercions
|
|
|
| # vim: ft=perl6
|
Any infix relational operator returning type Bool may be transformed into its negative by prefixing with !. A couple of these have traditional shortcuts:
Full form Shortcut
--------- --------
!== !=
!eq ne
but most of them do not:
!~~
!<
!>=
!ge
!===
!eqv
!=:=
To avoid visual confusion with the !! operator, you may not modify any operator already beginning with !.
The precedence of any negated operator is the same as the base operator.
The operator
!%
is specially allowed for testing even divisibility by an integer.
From t/spec/S03-metaops/not.t lines 52–69: (skip)
-
| # L<S03/"Negated relational operators"/"allowed for testing even
|
| # divisibility by an integer">
|
| {
|
| ok 6 !% 3, '6 !% 3';
|
| isa_ok 6 !% 3, Bool, '6 !% 3 isa Bool';
|
| nok 6 !% 4, '6 !% 4';
|
| isa_ok 6 !% 4, Bool, '6 !% 4 isa Bool';
|
|
|
| is (1..10).grep({ $_ !% 3 }), <3 6 9>, '!% works with explicit closure';
|
| is (1..10).grep( * !% 3 ), <3 6 9>, '!% works with whatever *';
|
| }
|
|
|
|
|
|
|
|
|
| done_testing;
|
|
|
| # vim: ft=perl6
|
Note that logical operators such as || and ^^ do not return a Bool, but rather one of the operands.
Any infix operator may be called with its two arguments reversed by prefixing with R. For instance, to do reversed comparisons:
Rcmp
Rleg
R<=>
The precedence of any reversed operator is the same as the base operator. The associativity is not reversed, so
[R-] 1,2,3 # produces 2 from 3 - (2 - 1)
To get the other effect in a reduce, reverse the list:
[-] reverse 1,2,3 # produces 0
From t/spec/S03-metaops/hyper.t lines 13–690: (skip)
-
| # L<S03/Hyper operators>
|
| # binary infix
|
| my @r;
|
| my @e;
|
| {
|
| @r = (1, 2, 3) »+« (2, 4, 6);
|
| @e = (3, 6, 9);
|
| is(~@r, ~@e, "hyper-sum two arrays");
|
|
|
| @r = (1, 2, 3) »-« (2, 4, 6);
|
| @e = (-1, -2, -3);
|
| is(~@r, ~@e, "hyper-subtract two arrays");
|
|
|
| @r = (1, 2, 3) »*« (2, 4, 6);
|
| @e = (2, 8, 18);
|
| is(~@r, ~@e, "hyper-multiply two arrays");
|
|
|
| @r = (1, 2, 3) »x« (3, 2, 1);
|
| @e = ('111', '22', '3');
|
| is(~@r, ~@e, "hyper-x two arrays");
|
|
|
| @r = (1, 2, 3) »xx« (3, 2, 1);
|
| @e = ((1,1,1), (2,2), (3));
|
| is(~@r, ~@e, "hyper-xx two arrays");
|
|
|
| @r = (20, 40, 60) »div« (2, 5, 10);
|
| @e = (10, 8, 6);
|
| is(~@r, ~@e, "hyper-divide two arrays");
|
|
|
| @r = (1, 2, 3) »+« (10, 20, 30) »*« (2, 3, 4);
|
| @e = (21, 62, 123);
|
| is(~@r, ~@e, "precedence - »+« vs »*«");
|
| }
|
|
|
| {
|
| @r = (1, 2, 3) >>+<< (2, 4, 6);
|
| @e = (3, 6, 9);
|
| is(~@r, ~@e, "hyper-sum two arrays ASCII notation");
|
|
|
| @r = (1, 2, 3) >>-<< (2, 4, 6);
|
| @e = (-1, -2, -3);
|
| is(~@r, ~@e, "hyper-subtract two arrays ASCII notation");
|
|
|
| @r = (1, 2, 3) >>*<< (2, 4, 6);
|
| @e = (2, 8, 18);
|
| is(~@r, ~@e, "hyper-multiply two arrays ASCII notation");
|
|
|
| @r = (1, 2, 3) >>x<< (3, 2, 1);
|
| @e = ('111', '22', '3');
|
| is(~@r, ~@e, "hyper-x two arrays ASCII notation");
|
|
|
| @r = (1, 2, 3) >>xx<< (3, 2, 1);
|
| @e = ((1,1,1), (2,2), (3));
|
| is(~@r, ~@e, "hyper-xx two arrays ASCII notation");
|
|
|
| @r = (20, 40, 60) >>div<< (2, 5, 10);
|
| @e = (10, 8, 6);
|
| is(~@r, ~@e, "hyper-divide two arrays ASCII notation");
|
|
|
| @r = (1, 2, 3) >>+<< (10, 20, 30) >>*<< (2, 3, 4);
|
| @e = (21, 62, 123);
|
| is(~@r, ~@e, "precedence - >>+<< vs >>*<< ASCII notation");
|
| };
|
|
|
| { # unary postfix
|
| my @r = (1, 2, 3);
|
| @r»++;
|
| my @e = (2, 3, 4);
|
| #?pugs todo
|
| is(~@r, ~@e, "hyper auto increment an array");
|
|
|
| @r = (1, 2, 3);
|
| @r>>++;
|
| @e = (2, 3, 4);
|
| #?pugs todo
|
| is(~@r, ~@e, "hyper auto increment an array ASCII notation");
|
| };
|
|
|
| { # unary prefix
|
| my @r;
|
| @r = -« (3, 2, 1);
|
| my @e = (-3, -2, -1);
|
| is(~@r, ~@e, "hyper op on assignment/pipeline");
|
|
|
| @r = -<< (3, 2, 1);
|
| @e = (-3, -2, -1);
|
| is(~@r, ~@e, "hyper op on assignment/pipeline ASCII notation");
|
| };
|
|
|
| { # dimension upgrade - ASCII
|
| my @r;
|
| @r = (1, 2, 3) >>+>> 1;
|
| my @e = (2, 3, 4);
|
| is(~@r, ~@e, "auto dimension upgrade on rhs ASCII notation");
|
|
|
| @r = 2 <<*<< (10, 20, 30);
|
| @e = (20, 40, 60);
|
| is(~@r, ~@e, "auto dimension upgrade on lhs ASCII notation");
|
| }
|
|
|
| { # extension
|
| @r = (1,2,3,4) >>~>> <A B C D E>;
|
| @e = <1A 2B 3C 4D>;
|
| is(~@r, ~@e, "list-level element truncate on rhs ASCII notation");
|
|
|
| @r = (1,2,3,4,5) <<~<< <A B C D>;
|
| @e = <1A 2B 3C 4D>;
|
| is(~@r, ~@e, "list-level element truncate on lhs ASCII notation");
|
|
|
| @r = (1,2,3,4) >>~>> <A B C>;
|
| @e = <1A 2B 3C 4A>;
|
| is(~@r, ~@e, "list-level element extension on rhs ASCII notation");
|
|
|
| @r = (1,2,3) <<~<< <A B C D>;
|
| @e = <1A 2B 3C 1D>;
|
| is(~@r, ~@e, "list-level element extension on lhs ASCII notation");
|
|
|
| @r = (1,2,3,4) >>~>> <A B>;
|
| @e = <1A 2B 3A 4B>;
|
| is(~@r, ~@e, "list-level element extension on rhs ASCII notation");
|
|
|
| @r = (1,2) <<~<< <A B C D>;
|
| @e = <1A 2B 1C 2D>;
|
| is(~@r, ~@e, "list-level element extension on lhs ASCII notation");
|
|
|
| @r = (1,2,3,4) >>~>> <A>;
|
| @e = <1A 2A 3A 4A>;
|
| is(~@r, ~@e, "list-level element extension on rhs ASCII notation");
|
|
|
| @r = (1,) <<~<< <A B C D>;
|
| @e = <1A 1B 1C 1D>;
|
| is(~@r, ~@e, "list-level element extension on lhs ASCII notation");
|
|
|
| @r = (1,2,3,4) >>~>> 'A';
|
| @e = <1A 2A 3A 4A>;
|
| is(~@r, ~@e, "scalar element extension on rhs ASCII notation");
|
|
|
| @r = 1 <<~<< <A B C D>;
|
| @e = <1A 1B 1C 1D>;
|
| is(~@r, ~@e, "scalar element extension on lhs ASCII notation");
|
| };
|
|
|
| { # dimension upgrade - unicode
|
| @r = (1,2,3,4) »~» <A B C D E>;
|
| @e = <1A 2B 3C 4D>;
|
| is(~@r, ~@e, "list-level element truncate on rhs unicode notation");
|
|
|
| @r = (1,2,3,4,5) «~« <A B C D>;
|
| @e = <1A 2B 3C 4D>;
|
| is(~@r, ~@e, "list-level element truncate on lhs unicode notation");
|
|
|
| @r = (1,2,3,4) »~» <A B C>;
|
| @e = <1A 2B 3C 4A>;
|
| is(~@r, ~@e, "list-level element extension on rhs unicode notation");
|
|
|
| @r = (1,2,3) «~« <A B C D>;
|
| @e = <1A 2B 3C 1D>;
|
| is(~@r, ~@e, "list-level element extension on lhs unicode notation");
|
|
|
| @r = (1,2,3,4) »~» <A B>;
|
| @e = <1A 2B 3A 4B>;
|
| is(~@r, ~@e, "list-level element extension on rhs unicode notation");
|
|
|
| @r = (1,2) «~« <A B C D>;
|
| @e = <1A 2B 1C 2D>;
|
| is(~@r, ~@e, "list-level element extension on lhs unicode notation");
|
|
|
| @r = (1,2,3,4) »~» <A>;
|
| @e = <1A 2A 3A 4A>;
|
| is(~@r, ~@e, "list-level element extension on rhs unicode notation");
|
|
|
| @r = (1,) «~« <A B C D>;
|
| @e = <1A 1B 1C 1D>;
|
| is(~@r, ~@e, "list-level element extension on lhs unicode notation");
|
|
|
| @r = (1,2,3,4) »~» 'A';
|
| @e = <1A 2A 3A 4A>;
|
| is(~@r, ~@e, "scalar element extension on rhs unicode notation");
|
|
|
| @r = 1 «~« <A B C D>;
|
| @e = <1A 1B 1C 1D>;
|
| is(~@r, ~@e, "scalar element extension on lhs unicode notation");
|
| };
|
|
|
| { # unary postfix with integers
|
| my @r;
|
| @r = (1, 4, 9)».sqrt;
|
| my @e = (1, 2, 3);
|
| is(~@r, ~@e, "method call on integer list elements");
|
|
|
| @r = (1, 4, 9)>>.sqrt;
|
| @e = (1, 2, 3);
|
| is(~@r, ~@e, "method call on integer list elements (ASCII)");
|
| }
|
|
|
| #?rakudo skip '@array»++'
|
| {
|
| my (@r, @e);
|
| (@r = (1, 4, 9))»++;
|
| @e = (2, 5, 10);
|
| is(~@r, ~@e, "operator call on integer list elements");
|
|
|
| (@r = (1, 4, 9)).»++;
|
| is(~@r, ~@e, "operator call on integer list elements (Same thing, dot form)");
|
|
|
| (@r = (1, 4, 9))».++;
|
| @e = (2, 5, 9);
|
| is(~@r, ~@e, "operator call on integer list elements (Same thing, dot form)");
|
|
|
| (@r = (1, 4, 9)).».++;
|
| is(~@r, ~@e, "operator call on integer list elements (Same thing, dot form)");
|
|
|
| (@r = (1, 4, 9))\ .»\ .++;
|
| @e = (2, 5, 9);
|
| is(~@r, ~@e, "operator call on integer list elements (Same thing, upspace form)");
|
| };
|
|
|
| { # unary postfix again, but with a twist
|
| my @r;
|
| eval '@r = ("f", "oo", "bar")».chars';
|
| my @e = (1, 2, 3);
|
| is(~@r, ~@e, "method call on list elements");
|
|
|
| eval '@r = ("f", "oo", "bar").».chars';
|
| @e = (1, 2, 3);
|
| is(~@r, ~@e, "method call on list elements (Same thing, dot form)");
|
|
|
|
|
| eval '@r = ("f", "oo", "bar")>>.chars';
|
| @e = (1, 2, 3);
|
| is(~@r, ~@e, "method call on list elements (ASCII)");
|
|
|
| eval '@r = ("f", "oo", "bar").>>.chars';
|
| @e = (1, 2, 3);
|
| is(~@r, ~@e, "method call on list elements (ASCII, Same thing, dot form)");
|
|
|
| };
|
|
|
| { # unary postfix on a user-defined object
|
| my $t;
|
| class FooTest { method bar { 42 } }; $t = FooTest.new.bar;
|
| is($t, 42, 'plain method call works OK');
|
|
|
| my @r;
|
| class FooTest2 { method bar { 42 } }; @r = (FooTest2.new)>>.bar;
|
| my @e = (42);
|
| is(~@r, ~@e, "hyper-method-call on list of user-defined objects");
|
| };
|
|
|
| { # distribution for unary prefix
|
| my @r;
|
| @r = -« ([1, 2], [3, [4, 5]]);
|
| my @e = ([-1, -2], [-3, [-4, -5]]);
|
| is(~@r, ~@e, "distribution for unary prefix");
|
| is_deeply(@r, @e, "distribution for unary prefix, deep comparison");
|
|
|
| @r = -<< ([1, 2], [3, [4, 5]]);
|
| @e = ([-1, -2], [-3, [-4, -5]]);
|
| is(~@r, ~@e, "distribution for unary prefix, ASCII");
|
| is_deeply(@r, @e, "distribution for unary prefix, ASCII, deep comparison");
|
| };
|
|
|
| { # distribution for unary postfix autoincrement
|
| my @r;
|
| @r = ([1, 2], [3, [4, 5]]);
|
| @r»++;
|
| my @e = ([2, 3], [4, [5, 6]]);
|
| #?pugs todo
|
| is(~@r, ~@e, "distribution for unary postfix autoincr");
|
| is_deeply(@r, @e, "distribution for unary postfix autoincr, deep comparison");
|
|
|
| @r = ([1, 2], [3, [4, 5]]);
|
| @r>>++;
|
| @e = ([2, 3], [4, [5, 6]]);
|
| #?pugs todo
|
| is(~@r, ~@e, "distribution for unary postfix autoincr, ASCII");
|
| is_deeply(@r, @e, "distribution for unary postfix autoincr, ASCII, deep comparison");
|
| };
|
|
|
| #?DOES 3
|
| { # distribution for binary infix - ASCII
|
| my @r;
|
| @r = (1, 2, [3, 4]) >>+<< (4, 5, [6, 7]);
|
| my @e = (5, 7, [9, 11]);
|
| is(~@r, ~@e, "distribution for binary infix, same shape, ASCII");
|
| is_deeply(@r, @e, "distribution for binary infix, same shape, ASCII, deep comparision");
|
|
|
| @r = (1, 2, [3, 4]) >>+>> (5, 6, 7);
|
| @e = (6, 8, [10, 11]);
|
| is(~@r, ~@e, "distribution for binary infix, dimension upgrade, ASCII");
|
| is_deeply(@r, @e, "distribution for binary infix, dimension upgrade, ASCII, deep comparison");
|
|
|
| @r = ([1, 2], 3) <<+>> (4, [5, 6]);
|
| @e = ([5, 6], [8, 9]);
|
| is(~@r, ~@e, "distribution for binary infix, S03 cross-upgrade, ASCII");
|
| is_deeply(@r, @e, "distribution for binary infix, S03 cross-upgrade, ASCII, deep comparison");
|
| };
|
|
|
| #?DOES 3
|
| { # distribution for binary infix - unicode
|
| my @r;
|
| @r = (1, 2, [3, 4]) »+« (4, 5, [6, 7]);
|
| my @e = (5, 7, [9, 11]);
|
| is(~@r, ~@e, "distribution for binary infix, same shape");
|
| is_deeply(@r, @e, "distribution for binary infix, same shape, deep comparison");
|
|
|
| @r = (1, 2, [3, 4]) »+» (5, 6, 7);
|
| @e = (6, 8, [10, 11]);
|
| is(~@r, ~@e, "distribution for binary infix, dimension upgrade");
|
| is_deeply(@r, @e, "distribution for binary infix, dimension upgrade, deep comparison");
|
|
|
| @r = ([1, 2], 3) «+» (4, [5, 6]);
|
| @e = ([5, 6], [8, 9]);
|
| is(~@r, ~@e, "distribution for binary infix, S03 cross-upgrade");
|
| is_deeply(@r, @e, "distribution for binary infix, S03 cross-upgrade, deep comparison");
|
| };
|
|
|
| { # regression test, ensure that hyper works on arrays
|
| my @r1;
|
| my @r2;
|
| my @e1 = (2, 4, 6);
|
| my @a = (1, 2, 3);
|
| @r1 = @a >>+<< @a;
|
| is(~@r1, ~@e1, "hyper op works on variables, too.");
|
| }
|
| {
|
| my @a = (1, 2, 3);
|
| my @e2 = (2, 3, 4);
|
| my @r2 = @a >>+>> 1;
|
| is(~@r2, ~@e2, "hyper op and correctly promotes scalars");
|
| };
|
|
|
|
|
| # mixed hyper and reduce metaops -
|
| # this unveils a spec bug as << recurses into arrays and [+] never gets applied,
|
| # so we disable the entire chunk for now.
|
| =begin todo unspecced
|
|
|
| is ~([+]<< ([1,2,3], [4,5,6])), "6 15", "mixed hyper and reduce metaop ([+]<<) works";
|
| ## XXX: Test for [+]<<<< - This is unspecced, commenting it out
|
| #is ~([+]<<<< ([[1,2],[3,4]],[[5,6],[7,8]])), "3 7 11 15",
|
| # "mixed double hyper and reduce metaop ([+]<<<<) works";
|
|
|
| is ~([+]« [1,2,3], [4,5,6]), "6 15",
|
| "mixed Unicode hyper and reduce metaop ([+] |