Raku의 연산자

Raku의 연산자 (Operators)

새 연산자를 정의하는 방법은 나만의 연산자 만들기 문서를 참고해요.

출처: Raku 공식 문서 — Operators

Raku의 연산자는 다른 언어들과는 달리 그 자체가 메타연산자(metaoperator)로 확장 가능하다는 점이 가장 큰 특징이에요. 단순히 +, * 같은 기호가 아니라, 연산자의 우선순위(precedence)와 결합성(associativity)까지 문법의 일부로 다루어져요. 이 문서에서는 연산자의 우선순위 표, 결합성, 분류, 그리고 각 연산자 그룹의 동작을 상세히 다룰게요.


연산자 우선순위 (Operator precedence)

Raku 연산자의 우선순위와 결합성은 표현식이 어떻게 파싱될지, 즉 표현식을 평가할 때 어떤 피연산자가 어떤 연산자에 전달될지를 결정해요. (괄호를 쓰면 부분 표현식을 하나의 피연산자로 취급할 수도 있어요.)

어떤 피연산자가 두 연산자에 동시에 속할 수 있는 위치에 놓인 경우(예: 1 + 2 * 3에서의 2), 그 피연산자는 우선순위가 더 높은 연산자에 속해요. 여기서는 * 연산자이죠. 따라서 +의 피연산자는 12 * 3이 되어 1 + 2 * 31 + (2 * 3)과 동등하고, 결과는 7이 돼요. 반면 1 ** 2 * 3(1 ** 2) * 3과 동등하고 3이 되는데, 그 이유는 거듭제곱 연산자 ***보다 우선순위가 높기 때문이에요.

"우선순위" 대신 "결합(binding)"이라는 말을 쓰기도 해요. 우선순위가 높은 연산자는 해당 피연산자에 **더 단단히 결합(tighter binding)**한다고 하고, 우선순위가 낮은 연산자는 **더 느슨하게 결합(looser binding)**한다고 해요.

다음 표는 Raku가 제공하는 우선순위 수준을 높은 것부터 낮은 것 순서로 요약한 것이에요. 각 수준에 대해 결합성과 예시 연산자를 함께 보여줘요.

우선순위 수준 결합성 예시
Term¹ non 42 3.14 "eek" qq["foo"] $x :!verbose @$array rand time now ∅
Method call left .meth .+ .? .* .() .[] .{} .<> .«» .:: .= .^ .: i
Autoincrement non ++ --
Exponentiation right **
Symbolic unary left ! + - ~ ? | +^ ~^ ?^ ^ //
Dotty infix¹ left .= .
Multiplicative left * × / ÷ % %% +& +< +> ~& ~< ~> ?& div mod gcd lcm
Additive left `+ - − +
Replication left x xx
Concatenation list ~ o ∘
Junctive and list & (&) (.) ∩ ⊍
Junctive or list `
Named unary¹ left temp let
Structural non but does <=> leg unicmp cmp coll .. ..^ ^.. ^..^
Chaining chain != ≠ == ⩵ < <= ≤ > >= ≥ eq ne lt le gt ge ~~ === ⩶ eqv !eqv =~= ≅ (elem) (cont) (<) (>) (<=) (>=) (<+) (>+) (==) ∈ ∊ ∉ ∋ ∍ ∌ ≡ ≢ ⊂ ⊄ ⊃ ⊅ ⊆ ⊈ ⊇ ⊉ ≼ ≽
Tight and list &&
Tight or list `
Conditional¹ right ?? !! ff ff^ ^ff ^ff^ fff fff^ ^fff ^fff^
Item assignment right =² => += -= **= xx=
Loose unary left so not
Comma list , :
List infix list Z minmax X X~ X* Xeqv ... … ...^ …^ ^... ^… ^...^ ^…^
List prefix right =³ ??? !!! ... [+] [*] Z=
Loose and list and andthen notandthen
Loose or list or xor orelse
Sequencer¹ list <== ==> <<== ==>>
Terminator¹ non ; {...} unless extra ) ] }

참고:


연산자 결합성 (Operator associativity)

결합성은 **동률 해소(tiebreaker)**로 볼 수 있어요. 같은 우선순위를 가진 두 연산자가 하나의 피연산자에 작용할 때, 그 피연산자가 어느 연산자에 속하는지를 결합성이 결정해요. 예를 들어 100 / 2 * 10에서 나눗셈 /과 곱셈 *은 우선순위가 같으므로, 2가 어느 연산자에 속할지는 결합성이 결정해요. 두 연산자가 모두 **좌결합(left associative)**이므로 연산이 왼쪽부터 (100 / 2) * 10처럼 묶여 결과는 500이 돼요. 반대로 둘 다 우결합이라면 100 / (2 * 10)로 묶여 5가 됐을 거예요.

다음 표는 같은 우선순위·같은 결합성을 가진 가상의 중위 연산자 § 세 개가 포함된 표현식이 각 결합성에 따라 어떻게 해석되는지 보여줘요.

결합성 $a § $b § $c § $d 의 의미
left (($a § $b) § $c) § $d
right $a § ($b § ($c § $d))
non ILLEGAL (불법)
chain ($a § $b) and ($b § $c) and ($c § $d)
list infix:<§>($a, $b, $c, $d)

이 표는 중위 연산자만 다루지만, 결합성은 다른 종류의 연산자가 쓰일 때도 표현식 구조를 결정해요. (참고: 현재 중위 연산자의 결합성만 설정할 수 있어요.) 예를 들어 equiv(&prefix:<~>) 우선순위를 가진 중위 § 연산자를 만들었다고 해볼게요. ~$a § $b에서 $a§의 왼쪽 피연산자이거나 ~의 (유일한) 피연산자일 수 있는데, 어느 쪽이 되느냐는 §에 부여한 결합성에 따라 달라져요. §이 좌결합이면 $a~의 피연산자가 되고 ~$a§의 왼쪽 피연산자가 되어 (~$a) § $b와 동등해져요. 우결합이면 $a§의 왼쪽 피연산자가 되어 ~($a § $b)와 동등해져요.

두 연산자가 같은 우선순위지만 다른 결합성을 가진 경우 피연산자 묶기는 더 복잡해져요. 다만 Raku에 내장된 연산자들 중 같은 우선순위 수준을 가진 것들은 모두 같은 결합성을 가져요.

비중위 연산자의 결합성 설정은 아직 구현되지 않았어요.

아래 연산자 설명에서 기본 결합성은 left로 가정할게요.


연산자 분류 (Operator classification)

연산자는 term을 기준으로 여러 위치에 나타날 수 있어요.

형식 이름
+term prefix (전치)
term1 + term2 infix (중위)
term++ postfix (후치)
(term) circumfix (포위)
term1[term2] postcircumfix (후포위)
.+(term) method (메서드)

각 연산자(메서드 연산자 제외)는 서브루틴으로도 사용 가능해요. 해당 루틴의 이름은 연산자 범주 뒤에 콜론, 그 다음 연산자를 이루는 기호를 담은 리스트 인용 구조로 만들어져요.

infix:<+>(1, 2);                # 1 + 2 와 동일
circumfix:«[ ]»(<a b c>);       # [<a b c>] 와 동일

특수한 경우로, listop(리스트 연산자)은 term처럼 서거나 prefix처럼 설 수 있어요. 서브루틴 호출이 가장 흔한 listop이에요. 다른 경우로는 메타환원된 중위 연산자([+] 1, 2, 3)와 listop ... 같은 스텁 연산자가 있어요.

사용자 정의 연산자는 Defining operators functions에서 다뤄요.


대입(치환) 연산자 (Substitution operators)

각 치환 연산자에는 두 가지 주요 형태가 있어요. 소문자 형태(예: s///)는 제자리(in-place, 파괴적) 동작을 하고, 대문자 형태(예: S///)는 비파괴적(non-destructive) 동작을 해요.

s/// 제자리 치환

my $str = 'old string';
$str ~~ s/o .+ d/new/;
say $str; # OUTPUT: «new string␤»

s///는 topic 변수 $_에 대해 동작하며 제자리에서 바꿔요. 주어진 Regex로 바꿀 부분을 찾아 제공된 치환 문자열로 바꿔요. $/Match 객체로 설정하거나, 여러 번 매치됐다면 Match 객체들의 List로 설정해요. $/를 반환해요.

이 연산자를 ~~ 스마트매치 연산자와 함께 쓰는 게 흔한데, 그 이유는 ~~가 왼쪽을 $_에 앨리어스하기 때문이에요.

정규표현식 캡처는 치환 부분에서 참조할 수 있어요. .subst 메서드와 같은 어드바를 받는데, 어드바는 s와 여는 / 사이에 (선택적 공백과 함께) 들어가요.

my $str = 'foo muCKed into the lEn';

# 두 번째 'o'를 'x'로 치환
$str ~~ s:2nd/o/x/;

# 'M' 또는 'L' 뒤에 비공백이 이어지는 부분을 'd'와 그 부분의 소문자로 치환
$str ~~ s :g :i/<[ML]> (\S+)/d{lc $0}/;

say $str; # OUTPUT: «fox ducked into the den␤»

다른 구분자를 쓸 수도 있어요.

my $str = 'foober';
$str ~~ s!foo!fox!;
$str ~~ s{b(.)r} = " d$0n";
say $str; # OUTPUT: «fox den␤»

짝이 없는 문자는 원래 슬래시를 그냥 대체하면 돼요. 중괄호 같은 짝이 있는 문자는 매치 부분에만 쓰고, 치환은 할당(문자열, 루틴 호출 등 무엇이든)으로 주어져요.

S/// 비파괴 치환

say S/o .+ d/new/ with 'old string';      # OUTPUT: «new string␤»
S:g/« (.)/$0.uc()/.say for <foo bar ber>; # OUTPUT: «Foo␤Bar␤Ber␤»

S///s/// 연산자와 같은 의미론을 쓰지만, 원래 문자열은 그대로 두고 결과 문자열을 반환해요($/s///와 같은 값으로 설정돼요).

참고: 결과가 반환값으로 얻어지므로, 이 연산자를 ~~ 스마트매치와 함께 쓰는 것은 실수이고 경고가 발생해요. $_가 아닌 변수에 치환을 수행하려면 given, with 등으로 $_에 앨리어스하거나, .subst 메서드를 쓰세요.

tr/// 제자리 문자 변환(transliteration)

my $str = 'old string';
$str ~~ tr/dol/wne/;
say $str; # OUTPUT: «new string␤»

tr///는 topic 변수 $_에 대해 동작하고 제자리에서 바꿔요. 단일 Pair 인자로 호출되는 Str.trans와 비슷하게 동작해요. Str.trans와 같은 어드바를 받아요. 원본 값과 결과 문자열 사이의 거리를 측정하는 StrDistance 객체를 반환해요.

my $str = 'old string';
$str ~~ tr:c:d/dol st//;
say $str; # OUTPUT: «old st␤»

TR/// 비파괴 문자 변환

with 'old string' {
    say TR/dol/wne/; # OUTPUT: «new string␤»
}

TR///tr/// 연산자와 같게 동작하지만, $_ 값을 건드리지 않고 결과 문자열을 반환해요.

say TR:d/dol // with 'old string'; # OUTPUT: «string␤»

할당 연산자 (Assignment operators)

Raku에는 다양한 할당 연산자가 있고, 크게 단순 할당 연산자와 복합 할당 연산자로 나눌 수 있어요.

단순 할당 연산자 기호는 =이에요. 쓰인 문맥에 따라 항목 할당 또는 리스트 할당을 의미할 수 있다는 점에서 '오버로드'돼 있어요.

my $x = 1;        # item assignment; $x = 1
my @x = 1,2,3;    # list assignment; @x = [1,2,3]

복합 할당 연산자는 메타연산자예요. 단순 할당 연산자 =와 중위 연산자를 결합해, 결과를 왼쪽 피연산자에 할당하기 전에 그 중위 연산자가 지정하는 연산을 수행하는 새 연산자를 만들어요. 내장 복합 할당 연산자의 예로 +=, -=, *=, /=, min=, ~=가 있어요. 동작 방식은 이래요.

my $a = 32;
$a += 10;         # $a = 42
$a -= 2;          # $a = 40

$a = 3;
$a min= 5;        # $a = 3
$a min= 2;        # $a = 2

my $s = 'a';
$s ~= 'b';        # $s = 'ab'

# 그리고 사용자 정의 연산자의 예:
sub infix:<space-concat> ($a, $b) { $a ~ " " ~ $b };
$a = 'word1';
$a space-concat= 'word2';                 # OUTPUT: «'word1 word2'␤»

단순·복합 할당 연산자의 공통점은 **할당 표현식(assignment expressions)**을 형성해 할당된 값을 반환/평가한다는 점이에요.

my sub fac (Int $n) { [*] 1..$n };        # 팩토리얼 계산 sub
my @x = ( my $y = fac(100), $y*101 );     # @x = [100!, 101!]

my $i = 0;
repeat { say $i } while ($i += 1) < 10;   # OUTPUT: «0,1,2,...9␤»

첫 예에서 할당 표현식 my $y = fac(100)$y를 선언하고 fac(100) 값을 할당한 뒤, 마지막으로 할당된 값 fac(100)을 반환해요. 그 반환값은 리스트 구성에 반영돼요. 둘째 예에서 복합 할당 표현식 $i += 1$i + 1$i에 할당하고, 이어서 할당된 값 $i+1로 평가되어 그 반환값을 while 루프 조건 판정에 쓸 수 있어요.

다음 두 문장이 (항상) 동등하다고 생각하기 쉽지만, 그렇지 않아요.

expression1 += expression2;                     # compound assignment
expression1  = expression1 + expression2;       # simple assignment

두 가지 이유 때문이에요. 첫째, 복합 할당 문의 expression1한 번만 평가되지만, 단순 할당 문의 expression1두 번 평가돼요. 둘째, 복합 할당 문은 (해당 중위 연산자에 따라) expression1이 정의되지 않은 값의 변수라면 암묵적으로 초기화할 수 있는데, 단순 할당 문에서는 그런 초기화가 일어나지 않아요.

첫 번째 차이는 프로그래밍 언어에서 흔하며 대체로 자명해요. 복합 할당에서는 할당 대상이 덧셈의 항이자 결과 저장 위치를 겸하므로 두 번 평가할 필요가 없어요. 반면 단순 할당은 더 일반적이어서, 덧셈 항으로 쓰이는 expression1의 값과 결과 저장 위치를 정의하는 expression1의 값이 꼭 같을 필요가 없어요. 그래서 두 표현식은 각각 평가돼요. 이 구분은 expression1의 평가에 부수 효과(변수 변경)가 있을 때 특히 중요해요.

my @arr = [10, 20, 30];
my $i = 0;

if rand < 1/2 {
    @arr[++$i] += 1;                # @arr = [10,21,30]
} else {
    @arr[++$i] = @arr[++$i] + 1;    # @arr = [10,31,30] (또는 [10,20,21]?)
}                                   # 결과는 구현에 따라 다를 수 있음
say @arr;

두 번째 차이는 **누산기 패턴(accumulator patterns)**에서 복합 할당 연산자를 널리 쓰는 관행과 관련돼요. 루프에서 일련의 값의 합이나 곱을 계산하는 누산기라는 변수를 쓰는데, 명시적 초기화의 필요를 없애기 위해 Raku의 복합 할당 연산자는 합리적으로 가능한 곳에서 초기화를 조용히 처리해줘요.

my @str = "Cleanliness is next to godliness".comb;
my ($len, $str);
for @str -> $c {
  $len += 1;
  $str ~= $c;
}
say "The string '$str' has $len characters.";

이 예에서 누산기 $len$str은 각각 0""으로 암묵적으로 초기화되는데, 이는 초기화 값이 연산자마다 다르다는 걸 보여줘요. 모든 복합 할당 연산자가 정의되지 않은 왼쪽 변수를 합리적으로 초기화할 수 있는 건 아니에요. 예컨대 /= 연산자는 임의로 피제수 값을 고르지 않고 예외를 던져요.

엄밀히 연산자는 아니지만, 메서드를 복합 할당 연산자처럼 쓸 수도 있어요.

my $a = 3.14;
$a .= round;      # $a = $a.round; OUTPUT: «3»

메타연산자 (Metaoperators)

메타연산자는 함수가 함수를 매개변수로 받는 것과 같은 방식으로 다른 연산자나 서브루틴으로 매개변수화될 수 있어요. 서브루틴을 매개변수로 쓰려면 이름 앞에 &를 붙이세요. Raku가 실제 결합 연산자를 백그라운드에서 생성하므로, 사용자 정의 연산자에도 이 메커니즘을 적용할 수 있어요. 체이닝된 메타연산자를 구분하려면 안쪽 연산자를 대괄호로 감싸세요.

부정된 관계 연산자 (Negated relational operators)

Bool을 반환하는 관계 연산자의 결과는 !를 앞에 붙여 부정할 수 있어요. !! 연산자와의 시각적 혼동을 피하기 위해, 이미 !로 시작하는 연산자는 수정할 수 없어요.

!==!eq의 단축 표기로 !=ne가 있어요.

my $a = True;
say so $a != True;    # OUTPUT: «False␤»

my $release = Date.new(:2015year, :12month, :24day);
my $today = Date.today;
say so $release !before $today;     # OUTPUT: «False␤»

역전 연산자 (Reversed operators)

어떤 중위 연산자든 R을 앞에 붙여 두 인자의 순서를 뒤집어 호출할 수 있어요. 피연산자의 결합성도 함께 뒤집혀요.

say 4 R/ 12;               # OUTPUT: «3␤»
say [R/] 2, 4, 16;         # OUTPUT: «2␤»
say [RZ~] <1 2 3>,<4 5 6>  # OUTPUT: «(41 52 63)␤»

하이퍼 연산자 (Hyper operators)

하이퍼 연산자는 «»(ASCII 변형은 <<, >>)를 포함해요. «/»로 감싼(또는 단항 연산자의 경우 앞이나 뒤에 둔) 주어진 연산자를 하나 또는 두 리스트에 적용해 결과 리스트를 반환해요. «»의 뾰족한 쪽이 더 짧은 리스트를 향해요. 단일 요소는 리스트로 변환되어 쓸 수 있어요. 한 리스트가 다른 리스트보다 짧으면, 긴 리스트의 모든 요소를 처리할 때까지 짧은 리스트를 순환하며 반복해요.

say (1, 2, 3) »*» 2;          # OUTPUT: «(2 4 6)␤»
say (1, 2, 3, 4) »~» <a b>;   # OUTPUT: «(1a 2b 3a 4b)␤»
say (1, 2, 3) »+« (4, 5, 6);  # OUTPUT: «(5 7 9)␤»
say (&sin, &cos, &sqrt)».(0.5);
# OUTPUT: «(0.479425538604203 0.877582561890373 0.707106781186548)␤»

마지막 예는 postcircumfix 연산자(여기서는 .())도 하이퍼화될 수 있음을 보여줘요.

my @a = <1 2 3>;
my @b = <4 5 6>;
say (@a,@b)»[1]; # OUTPUT: «(2 5)␤»

할당 메타연산자도 하이퍼화할 수 있어요.

my @a = 1, 2, 3;
say @a »+=» 1;    # OUTPUT: «[2 3 4]␤»
my ($a, $b, $c);
(($a, $b), $c) «=» ((1, 2), 3);
say "$a, $c";       #  OUTPUT: «1, 3␤»

단항 연산자의 하이퍼 형태는 뾰족한 쪽이 연산자를 향하고, 무딘 쪽이 연산 대상 리스트를 향해요.

my @wisdom = True, False, True;
say !« @wisdom;     # OUTPUT: «[False True False]␤»

my @a = 1, 2, 3;
@a»++;
say @a;             # OUTPUT: «[2 3 4]␤»

하이퍼 연산자는 중첩 배열에 대해 재귀적으로 정의돼요.

say -« [[1, 2], 3]; # OUTPUT: «[[-1 -2] -3]␤»

또한 메서드를 순서와 무관하게 동시에 호출할 수도 있어요. 결과 리스트는 순서대로 반환돼요. 모든 하이퍼 연산자는 병렬 실행 후보이며, 메서드에 부수 효과가 있으면 문제(tears)를 일으킬 수 있어요. 옵티마이저가 하이퍼 연산자를 완전히 통제하므로, 사용자가 하이퍼 연산자를 정의할 수는 없어요.

하이퍼 연산자는 해시와도 동작해요. 뾰족한 방향이 결과 해시에서 빠진 키를 무시할지 여부를 나타내요. 감싼 연산자는 두 해시에 모두 키가 있는 값들에 대해 동작해요.

표현 의미
%foo «+» %bar; 키의 교집합
%foo »+« %bar; 키의 합집합
%outer »+» %inner; %outer에 존재하는 %inner의 키만 결과에 나타남
my %outer = 1, 2, 3 Z=> <a b c>;
my %inner = 1, 2 Z=> <x z>;
say %outer «~» %inner;          # OUTPUT: «{"1" => "ax", "2" => "bz"}␤»

하이퍼 연산자는 사용자 정의 연산자를 연산자 인자로 받을 수 있어요.

sub pretty-file-size (Int $size --> Str) {
    # infix:</>(Int, Int)의 반올림 버전
    sub infix:<r/>(Int \i1, Int \i2) {
        round(i1 / i2, 0.1)
    }

    # $size의 분수 벡터를 만들고 적절한 접두사와 zip으로 묶기
    for $size «[r/]« (2**60, 2**50, 2**40, 2**30, 2**20, 2**10)
              Z      <EB     PB     TB     GB     MB     KB> -> [\v,\suffix] {
        # 가장 큰 접미사부터 시작해, 그 접미사의 0.5 이상인 첫 값을 취함
        return v ~ ' ' ~ suffix if v > 0.4
    }
    # 이 값은 0.4 KB 이하
    return $size.Str;
}

전치(prefix) 연산자

prefix | (슬립/인자 분해)

Capture, Pair, List, Map, Hash 타입의 객체를 인자 리스트로 펼쳐요.

sub slurpee( |args ){
    say args.raku
};
slurpee( <a b c d>, { e => 3 }, 'e' => 'f' => 33 )
# OUTPUT: «\(("a", "b", "c", "d"), {:e(3)}, :e(:f(33)))␤»

인자 리스트 밖에서는 Slip을 반환해 바깥 리스트로 펼쳐져요. 인자 리스트 안에서는 Positional이 위치 인자로, Associative가 이름 인자로 변환돼요.

prefix +^ (정수 비트 부정)

multi prefix:<+^>(Any --> Int:D)

[정수 비트 부정 연산자]: 숫자에 필요한 바이트 수 + 1만큼의 바이트로 이진 변환한 뒤 모든 비트를 뒤집고, 그 결과를 2의 보수 표현으로 간주해 반환해요.

say +^255; # OUTPUT: «-256␤»

이 경우 255는 11111111이라 1바이트면 돼요. 값에 필요한 바이트에 +1을 더해 0000 0000 1111 1111로 변환해요. 비트 부정은 1111 1111 0000 0000으로 만들고, 이는 2의 보수로 -256이므로 그 값을 반환해요.

say +^1;        # OUTPUT: «-2␤»
say +^(-256);   # OUTPUT: «255␤»

음수는 2의 보수로 표현된다고 가정되므로 다시 원래 숫자로 순환돼요.

prefix ~^

인자를 가변 인코딩이 아닌 문자열 버퍼 타입(예: buf8, buf16, buf32)으로 강제 변환한 뒤 그 버퍼의 각 비트를 뒤집어요. (아직 구현되지 않았어요.)

prefix ?^

multi prefix:<?^>(Mu --> Bool:D)

[불리언 비트 부정 연산자]: 인자를 Bool로 강제 변환한 뒤 비트를 뒤집어서, prefix:<!>와 동일해요.

prefix ^ (upto 연산자)

multi prefix:<^>(Any --> Range:D)

upto 연산자. 인자를 Numeric으로 강제 변환하고, 0부터 인자 직전까지의 범위를 생성해요.

say ^5;         # OUTPUT: «0..^5␤»
for ^5 { }      # 5회 반복

Dotty infix 우선순위

이 연산자들은 Method Postfix 대응물과 비슷하지만, 구분을 위해 주변에 공백(앞/뒤)을 필요로 해요.

infix .= (메서드 돌연변이)

왼쪽 컨테이너의 값에 대해 오른쪽의 메서드를 호출하고, 그 결과 값을 왼쪽 컨테이너에 다시 넣어요. 대부분 postfix .=과 동일하게 동작하지만 우선순위가 더 낮아요.

my $a = -5;
say ++$a.=abs; # with postfix mutator; equivalent to ++($a.=abs)
# OUTPUT: «6␤»
my $a = -5;
say ++$a .= abs; # with infix mutator; evaluated as -4 .= abs
# OUTPUT: «Cannot modify an immutable Int (-4)␤
#           in block <unit> at <tmp> line 1␤␤»

infix . (메서드 호출)

왼쪽 invocant에 대해 뒤따르는 (이름이 알파벳이어야 하는) 메서드를 호출해요. infix 형태는 postfix .meth보다 우선순위가 약간 낮아요.

say -5.abs;      # like: -(5.abs)
# OUTPUT: «-5␤»
say -5 . abs;    # like: (-5) . abs
# OUTPUT: «5␤»
say -5 .abs;     # following whitespace is optional
# OUTPUT: «5␤»

곱셈 우선순위 (Multiplicative precedence)

infix * (곱셈)

multi infix:<*>(Any, Any --> Numeric:D)

[곱셈 연산자]. 두 인자를 Numeric으로 강제 변환하고 곱해요. 결과는 더 넓은 타입이에요.

infix / (나눗셈)

multi infix:</>(Any, Any --> Numeric:D)

[나눗셈 연산자]. 두 인자를 Numeric으로 강제 변환하고 왼쪽을 오른쪽으로 나눠요. Int 값의 나눗셈은 Rat을 반환하고, 그 외에는 Numeric에서 설명하는 "넓은 타입" 규칙이 적용돼요. 정수 나눗셈에는 div도 있어요.

infix div (정수 나눗셈)

multi infix:<div>(Int:D, Int:D --> Int:D)

[정수 나눗셈 연산자]. 내림(rounds down)해요.

infix % (모듈로)

multi infix:<%>($x, $y --> Numeric:D)

[모듈로 연산자]. 먼저 Numeric으로 강제 변환해요. 일반적으로 다음 항등식이 성립해요.

my ($x, $y) = 1,2;
$x % $y == $x - floor($x / $y) * $y

정수 모듈로에는 mod도 있어요.

infix %% (나눗셈 가능성)

multi infix:<%%>($a, $b --> Bool:D)

[나눗셈 가능성 연산자]. $a % $b == 0이면 True를 반환해요.

infix mod (정수 모듈로)

multi infix:<mod>(Int:D $a, Int:D $b --> Int:D)

[정수 모듈로 연산자]. 정수 모듈로 연산의 나머지를 반환해요.

infix +& (숫자 비트 AND)

multi infix:<+&>($a, $b --> Int:D)

숫자 비트 AND 연산자. 두 인자를 Int로 강제 변환하고 2의 보수를 가정해 비트 AND를 수행해요.

infix +< / +> (정수 비트 시프트)

multi infix:«+<»($a, $b --> Int:D)
multi infix:«+>»($a, $b --> Int:D)

왼쪽/오른쪽 정수 비트 시프트.

infix ~& / ~< / ~>

각 인자를 가변 인코딩이 아닌 문자열 버퍼 타입(예: buf8, buf16, buf32)으로 강제 변환한 뒤, 두 버퍼의 대응 정수에 대해 숫자 비트 AND를 수행하고 짧은 버퍼는 0으로 패딩해요. ~<~>는 버퍼 비트를 왼쪽/오른쪽으로 시프트하는데, 아직 구현되지 않았어요.

infix ?& (불리언 논리 AND)

multi infix:<?&>(Mu $x = Bool::True)
multi infix:<?&>(Mu \a, Mu \b)

[불리언 논리 AND 연산자]. 인자(들)를 Bool로 강제 변환하고 논리 AND를 수행해요. 두 인자가 모두 True일 때만 True를 반환해요. 단일 인자에서는 항등처럼 동작해 강제 변환된 값을 반환해요.

infix gcd (최대공약수)

multi infix:<gcd>($a, $b --> Int:D)

두 인자를 Int로 강제 변환하고 최대공약수를 반환해요. 한 인자가 0이면 다른 쪽을 반환하고, 둘 다 0이면 0을 반환해요.

infix lcm (최소공배수)

multi infix:<lcm>($a, $b --> Int:D)

두 인자를 Int로 강제 변환하고 최소공배수, 즉 두 인자 모두로 균등히 나눠지는 가장 작은 정수를 반환해요.


덧셈 우선순위 (Additive precedence)

infix + (덧셈) / infix - (뺄셈)

multi infix:<+>($a, $b --> Numeric:D)
multi infix:<->($a, $b --> Numeric:D)

[덧셈/뺄셈 연산자]. 두 인자를 Numeric으로 강제 변환하고 더하거나 뺄셈해요. 6.d 버전부터 Duration, DateTime, Real 타입에도 동작해요.

infix +| / +^ (정수 비트 OR / XOR)

multi infix:<+|>($a, $b --> Int:D)
multi infix:<+^>($a, $b --> Int:D)

두 인자를 Int로 강제 변환하고 비트 OR(포함) 또는 비트 XOR(배타) 연산을 수행해요.

say (0b00001101 +^ 0b00001001).base(2); # OUTPUT: «100␤»

infix ~| / ~^

각 인자를 가변 인코딩이 아닌 문자열 버퍼 타입으로 강제 변환한 뒤 두 버퍼의 대응 정수에 대해 비트 OR/XOR을 수행하고 짧은 버퍼는 0으로 패딩해요.

infix ?^ / ?| (불리언 논리 XOR / OR)

multi infix:<?^>(Mu $x = Bool::False)
multi infix:<?^>(Mu \a, Mu \b)
multi infix:<?|>(Mu $x = Bool::False)
multi infix:<?|>(Mu \a, Mu \b)

인자(들)를 Bool로 강제 변환한다. ?^(XOR)는 인자 중 하나만 True일 때 True를, ?|(OR)는 적어도 하나가 True일 때 True를 반환해요. 단일 인자에서는 항등처럼 동작해요.


복제 우선순위 (Replication precedence)

infix x (문자열 반복)

sub infix:<x>($a, $b --> Str:D)

[문자열 반복 연산자]. 문자열 $a$b번 반복해요(필요하면 $aStr로, $bInt로 강제 변환). $b <= 0이면 빈 문자열을 반환하고, $b-InfNaN이면 X::Numeric::CannotConvert 예외를 던져요.

say 'ab' x 3;           # OUTPUT: «ababab␤»
say 42 x 3;             # OUTPUT: «424242␤»

my $a = 'a'.IO;
my $b = 3.5;
say $a x $b;            # OUTPUT: «aaa␤»

infix xx (리스트 반복)

multi infix:<xx>(Mu \x, Int:D $n)
...

[리스트 반복 연산자]. 일반적으로 $a$b번($bInt로 강제) 반복 평가한 Sequence를 반환해요. $b <= 0이면 빈 리스트를 반환해요. 왼쪽은 매 반복마다 평가되므로,

say [1, 2] xx 5;
# OUTPUT: «([1 2] [1 2] [1 2] [1 2] [1 2])␤»

는 다섯 개의 서로 다른 배열(내용은 같음)을 반환하고,

rand xx 3

는 독립적으로 결정된 세 개의 의사 난수를 반환해요. 오른쪽이 *이면 lazy 무한 리스트를 반환하고, Bool이면 True일 때 단일 요소 Seq를 반환해요.


연결 우선순위 (Concatenation)

infix ~ (문자열 연결)

multi infix:<~>(Any,   Any)
multi infix:<~>(Str:D, Str:D)
...

[문자열 연결 연산자]. 두 인자를 Str로 강제 변환하고 연결해요. 두 인자가 모두 Buf이면 합쳐진 버퍼를 반환해요.

say 'ab' ~ 'c';     # OUTPUT: «abc␤»
my $bob = Blob.new([1,2,3]);
my $bao = Blob.new([3,4,5]);
say $bao ~ $bob;     # OUTPUT: «Blob:0x<03 04 05 01 02 03>␤»

infix o, infix ∘ (함수 합성)

multi infix:<o>(&f, &g --> Block:D)

[함수 합성 연산자] infix:<∘> 또는 infix:<o>는 두 함수를 결합해, 오른쪽 함수의 반환값으로 왼쪽 함수를 호출해요. 왼쪽 함수의 .count가 1보다 크면 오른쪽 반환값이 왼쪽 함수로 슬립돼요.

sub f($p){ say 'f'; $p / 2 }
sub g($p){ say 'g'; $p * 2 }

my &composed = &f ∘ &g;
say composed 2; # OUTPUT: «g␤f␤2␤»

1-인자 후보는 주어진 인자를 그대로 반환하고, 0-인자 후보는 인자를 그대로 반환하는 항등 루틴을 반환해요.


Junctive AND (all) 우선순위

infix & (all 정션)

multi infix:<&>($a, $b --> Junction:D) is assoc<list>

[All 정션 연산자]. 인자들로 all Junction을 생성해요.

infix (&), infix ∩ (교집합)

multi infix:<(&)>(**@p)

[교집합 연산자]. 모든 인자의 교집합을 반환해요. 인자 중 어느 것도 Bag/BagHash/Mix/MixHash가 아니면, 모든 인자에 공통인 요소만 담은 새 Set을 생성해요.

say <a b c> (&) <b c d>; # OUTPUT: «Set(b c)␤»
say <a b c d> ∩ <b c d e> ∩ <c d e f>; # OUTPUT: «Set(c d)␤»

infix (.), infix ⊍ (Baggy 곱셈)

multi infix:<(.)>(**@p)

[Baggy 곱셈 연산자]. 인자들의 Baggy 곱셈을 반환해요. 인자 중 어떤 것이 MixyMix를 반환해요.

say <a b c> (.) <a b c d>; # OUTPUT: «Bag(a b c)␤»
say <a a b c a d> ⊍ bag(<a a b c c>); # OUTPUT: «Bag(a(6) b c(2))␤»

Junctive OR (any) 우선순위

infix | (any 정션)

multi infix:<|>($a, $b --> Junction:D) is assoc<list>

인자들로 any Junction을 생성해요.

my $three-letters = /<[a b c]>/ | /<[i j k]>/ | /<[x y z]>/;
say $three-letters.raku; # OUTPUT: «any(/<[a b c]>/, /<[i j k]>/, /<[x y z]>/)␤»
say 'b' ~~ $three-letters; # OUTPUT: «True␤»

infix (|), infix ∪ (합집합)

multi infix:<(|)>(**@p)

[합집합 연산자]. 모든 인자의 합집합을 반환해요. Baggy/Mixy 인자가 있으면 각 요소를 해당 요소에 나타난 최고 가중치로 가중한 새 Bag(또는 Mix)을 반환해요.

say <a b d> ∪ bag(<a a b c>); # OUTPUT: «Bag(a(2) b c d)␤»

infix (+), infix ⊎ (Baggy 덧셈)

multi infix:<(+)>(**@p)

[Baggy 덧셈 연산자]. 인자들의 Baggy 덧셈을 반환해요. 각 요소를 더한 가중치로 새 Bag을 생성해요.

say <a a b c a d> (+) <a a b c c>; # OUTPUT: «Bag(a(5) b(2) c(3) d)␤»

infix (-), infix ∖ (집합 차집합)

multi infix:<(-)>(**@p)

[집합 차집합 연산자]. 모든 인자의 집합 차를 반환해요. 첫 인자엔 있지만 나머지 인자엔 없는 요소만 담은 새 Set을 생성해요.

say <a a b c a d> (-) <a a b c c>; # OUTPUT: «Set(d)␤»
say <a a b c a d> (-) bag(<a b c c>); # OUTPUT: «Bag(a(2) d)␤»

infix ^ (one 정션)

multi infix:<^>($a, $b --> Junction:D) is assoc<list>

[One 정션 연산자]. 인자들로 one Junction을 생성해요.

infix (^), infix ⊖ (대칭 차집합)

multi infix:<(^)>($a, $b)
multi infix:<(^)>(**@p)

[대칭 집합 차 연산자]. 모든 인자의 대칭 집합 차를 반환해요. $a엔 있고 $b엔 없는 요소와 $b엔 있고 $a엔 없는 요소를 모두 담은 새 Set을 생성해요. ($a ∖ $b) ∪ ($b ∖ $a)와 동등해요.

say <a b> (^) <b c>; # OUTPUT: «Set(a c)␤»
say <a b> ⊖ bag(<b c>); # OUTPUT: «Bag(a c)␤»

Named unary 우선순위

prefix temp

sub prefix:<temp>(Mu $a is rw)

인자로 전달된 변수를 "일시적(temporizes)"으로 만들어요. 변수는 바깥 스코프에서 가진 값과 같은 값으로 시작하지만, 이 스코프에서 새 값을 할당할 수 있어요. 스코프를 벗어나면 변수는 원래 값으로 복원돼요.

my $a = "three";
say $a; # OUTPUT: «three␤»
{
    temp $a;
    say $a; # OUTPUT: «three␤»
    $a = "four";
    say $a; # OUTPUT: «four␤»
}
say $a; # OUTPUT: «three␤»

블록을 벗어나면 temp 효과가 제거된다는 점에 주의하세요. 블록이 떠난 뒤, 예를 들어 Promise 안에서 그 값을 접근하면 temp된 값이 아니라 원래 값을 얻게 돼요.

prefix let

sub prefix:<let>(Mu $a is rw)

외부 스코프의 변수를 가리키되, 블록이 실패(정의된 객체를 반환하지 않음)로 끝날 경우 값이 복원되게 해요.


구조적 비교 (Structural) 연산자

infix cmp (일반 비교)

또는 leg(문자열 비교) / <=>(숫자 비교). cmp는 인자의 기존 타입에 따라 <=> 또는 leg를 수행해요. Order::Less / Order::Same / Order::More 중 하나를 반환해요.

infix eqv (구조 동등)

sub infix:<eqv>(Any, Any)

[동등 연산자]라고 부를 수 있지만, 두 인자가 구조적으로 동일할 때 True를 반환해요. 같은 타입이고 (재귀적으로) 동등한 값을 담고 있을 때를 말해요. 사용자 정의 클래스는 이 연산자의 자체 구현을 제공할 수 있어요.

say [1, 2, 3] eqv [1, 2, 3];    # OUTPUT: «True␤»
say Any eqv Any;                # OUTPUT: «True␤»
say 1 eqv 2;                    # OUTPUT: «False␤»
say 1 eqv 1.0;                  # OUTPUT: «False␤»

lazy Iterable은 무한하다고 가정되므로 비교할 수 없어요. 다만 두 lazy Iterable의 타입이 다르거나 하나만 lazy이면 False를 반환하려 시도해요.

기본 eqv는 임의의 객체에서도 동작해요. 같은 객체의 두 인스턴스를 구조적으로 동등하다고 간주해요. 더 나은 성능을 위해 적절한 infix:<eqv>를 구현할 수도 있어요.

my class A {
    has $.a;
}
multi infix:<eqv>(A $l, A $r) { $l.a eqv $r.a }
say A.new(a => 5) eqv A.new(a => 5);            # OUTPUT: «True␤»

단일 인자 버전(전체 이름으로 호출)은 항상 True를 반환해요.

say infix:<eqv>(33);    # OUTPUT: «True␤»

infix === / infix ⩶ (값 정체성)

sub infix:<===>(Any, Any)
sub infix:<⩶>(Any, Any)

[값 정체성 연산자]. 두 인자가 컨테이너화를 무시하고 같은 객체일 때 True를 반환해요. 값 타입에서는 eqv처럼 동작해요. WHICH 메서드로 객체 정체성을 얻어요.

my class A { };
my $a = A.new;
say $a ⩶ $a;              # OUTPUT: «True␤»
say A.new ⩶ A.new;        # OUTPUT: «False␤»
say A ⩶ A;                # OUTPUT: «True␤»

say 1 ⩶ 1.0;              # OUTPUT: «False␤»

infix =:= (컨테이너 정체성)

multi infix:<=:=>(Mu \a, Mu \b)

[컨테이너 정체성 연산자]. 두 인자가 같은 컨테이너에 바인딩돼 있으면 True를 반환해요. True면 일반적으로 하나를 수정하면 다른 것도 수정된다는 뜻이에요.

my ($a, $b) = (1, 3);
say $a =:= $b;      # OUTPUT: «False␤»
$b := $a;
say $a =:= $b;      # OUTPUT: «True␤»
$a = 5;
say $b;             # OUTPUT: «5␤»

연쇄 비교 (Chaining) 연산자

연쇄 연산자는 ($a § $b) and ($b § $c)처럼도 평가돼요. 연쇄(chain) 결합성을 가진 비교 연산자들이 여기에 속해요.

infix ~~ (스마트매치)

스마트매치 연산자는 오른쪽의 .ACCEPTS() 메서드를 왼쪽을 인자로 해서 호출해요. 의미론은 오른쪽 피연산자의 타입에 맡겨져요. 내장 스마트매치 기능의 일부 목록:

오른쪽 비교 의미론
Mu:U 타입 검사
Str 문자열 동등
Numeric 숫자 동등
Regex 정규표현식 매치
Callable 호출의 불리언 결과
Set/Bag 동등한 요소 값
Any:D 객체 정체성

infix =~= / infix ≅ (대략 동등, approximately equal)

multi infix:<≅>(Numeric:D, Numeric:D)

[대략 동등 연산자]. 왼쪽과 오른쪽 사이의 상대 백분율 차이를 계산하고, 그 차이가 $*TOLERANCE(기본값 1e-15)보다 작으면 True를 반환해요. 단, 어느 한쪽이 0이면 절대 차이가 $*TOLERANCE보다 작은지 검사해요.

이 연산자는 산술적으로 대칭이 아니에요(± Δ를 하지 않음):

my $x = 1;
say ($x + $*TOLERANCE) ≅ $x;   # OUTPUT: «False␤»
say ($x - $*TOLERANCE) ≅ $x;   # OUTPUT: «True␤»

$*TOLERANCE를 어휘 범위에서 바꿀 수 있어요. $*TOLERANCE = 0으로 설정하면 모든 비교가 실패해요.

집합 연산자들 (elem, ∈, ∉, (==), ≡, ≢, (cont), ∋, ∌, (<), ⊂, ⊄, (<=), ⊆, ⊈, (>), ⊃, ⊅, (>=), ⊇, ⊉)

집합 관련 연산자들은 집합(Set)에 대한 소속·포함·부분집합 관계를 검사해요. (elem)/(소속), (비소속), (==)/(집합 동등), (집합 부등), (cont)/(포함), (비포함), (<)/(진부분집합), (진부분집합 아님), (<=)/(부분집합), (부분집합 아님), (>)/(진초집합), (진초집합 아님), (>=)/(초집합), (초집합 아님)이에요. 각각의 !() 형태와 동등한 부정이 존재해요.

say 2 (elem) (1, 2, 3); # OUTPUT: «True␤»
say 4 ∉ (1, 2, 3);      # OUTPUT: «True␤»
say (1, 2, 3) (==) (1, 3, 2); # OUTPUT: «True␤»
say (1,2,3) (cont) 2; # OUTPUT: «True␤»
say (2,3) (<) (2,3,1);   # OUTPUT: «True␤»
say (1,2,3) (<=) (2,3,1); # OUTPUT: «True␤»

//로 대체되어 v6.d에서 제거됐어요(비권장).

문자열 비교 연산자 (eq, ne, gt, ge, lt, le)

eq(같음), ne(같지 않음), gt(큼), ge(크거나 같음), lt(작음), le(작거나 같음) — 필요하면 두 인자를 Str로 강제 변환하고 사전식(lexicographic) 비교를 해요. 각각 "greater than", "greater or equal", "less than", "less or equal"의 기억법(mnemonic)을 가져요.

infix before / after (일반 순서)

multi infix:<before>(Any, Any)
multi infix:<after>(Any, Any)

일반 순서. cmp와 같은 의미론을 쓰며, 첫 인자가 둘째보다 작으면(before)/크면(after) True를 반환해요.


Tight AND / Tight OR 우선순위

infix && (단락 AND)

Boolean 문맥에서 False로 평가되는 첫 인자를 반환하고, 그렇지 않으면 마지막 인자를 반환해요. **단락(short-circuit)**돼요.

sub a { 1 }
sub b { 0 }
sub c { die "never called" };
say a() && b() && c();      # OUTPUT: «0␤»

infix || (단락 OR)

Boolean 문맥에서 True로 평가되는 첫 인자를 반환하고, 아니면 마지막 인자를 반환해요. 단락돼요.

sub a { 0 }
sub b { 1 }
sub c { die "never called" };
say a() || b() || c();      # OUTPUT: «1␤»

infix ^^ (단락 배타 OR)

[단락 배타 OR]. true 인자가 정확히 하나 있으면 그 인자를 반환하고, 모두 false면 마지막 인자를, true 인자가 둘 이상이면 Nil을 반환해요. 두 번째 true 이후에는 더 이상의 인자를 평가하지 않아요. "하나의 true 찾기" 의미론이지 "불리언 패리티" 의미론이 아니에요.

say 0 ^^ 42;                             # OUTPUT: «42␤»
say 0 ^^ 42 ^^ 1 ^^ die "never called";  # OUTPUT: «Nil␤»

infix // (정의된-또는)

[정의된-또는 연산자]. 첫 번째 정의된 피연산자를 반환하고, 아니면 마지막 피연산자를 반환해요. 단락돼요.

say Any // 0 // 42;         # OUTPUT: «0␤»

infix min / max (최소/최대)

cmp 의미론으로 판단해 인자 중 가장 작은/큰 값을 반환해요.

my $foo = 42;
$foo min= 0   # $foo decreases to 0 로 읽음

infix minmax

cmp 의미론으로 판단해 최저에서 최고까지의 Range를 반환해요.

10 minmax 3;     # 3..10
(10,20,30) minmax (0,11,22,33);       # 0..33

조건 연산자 우선순위 (Conditional)

infix ?? !! (삼항 연산자)

ternary 또는 conditional 연산자라고도 불러요. $condition ?? $true !! $false$condition을 평가해 True?? 뒤의 표현식($true)을, 아니면 !! 뒤의 표현식($false)을 평가·반환해요.

infix ff (플립플롭)

sub infix:<ff>(Mu $a, Mu $b)

flipflop 연산자라고도 불러요. 두 인자를 각각 $_와 비교해요($_ ~~ $a, $_ ~~ $b). 왼쪽 스마트매치가 True가 될 때까지 False로 평가하다가, 그 시점부터 오른쪽 스마트매치가 True일 때까지 True로 평가해요. 본질적으로 왼쪽이 "시작" 조건, 오른쪽이 "종료" 조건이에요. 보통 특정 구간의 줄만 잡아내는 데 쓰여요.

my $excerpt = q:to/END/;
Here's some unimportant text.
=begin code
    This code block is what we're after.
    We'll use 'ff' to get it.
=end code
More unimportant text.
END

my @codelines = gather for $excerpt.lines {
    take $_ if "=begin code" ff "=end code"
}
say @codelines.join("\n");

시작 조건만 테스트하고 종료 조건이 없다면 *를 쓸 수 있어요.

for <A B C D E> {
    say $_ if /C/ ff *;    # OUTPUT: «C␤D␤E␤»
}

이 연산자는 컴파일러가 특수 처리하므로 오버로드할 수 없어요.

infix ^ff / ff^ / ^ff^ (ff 변형)

^ff는 시작 조건을 매치한 항목(종료 조건도 매치하는 항목 포함)에 대해 True를 반환하지 않아요. ff^는 종료 조건을 매치한 항목에 대해 True를 반환하지 않아요. ^ff^는 어느 조건도 매치하지 않은 항목에 대해서만 동작해요.

my @list = <X A B C Y>;
say $_ if /A/ ff /C/ for @list;    # OUTPUT: «A␤B␤C␤»
say $_ if /A/ ^ff /C/ for @list;   # OUTPUT: «B␤C␤»
say $_ if /A/ ff^ /C/ for @list;   # OUTPUT: «A␤B␤»
say $_ if /A/ ^ff^ /C/ for @list;  # OUTPUT: «B␤»

infix fff (sed형 플립플롭)

sed류의 플립플롭. ff와 비슷하지만 호출당 한 인자만 시도해요. 즉 $_가 왼쪽 인자를 매치해도 같은 $_를 오른쪽 인자에 대해 시도하지 않아요.

for <AB C D B E F> {
    say $_ if /A/ fff /B/;         # OUTPUT: «AB␤C␤D␤B␤»
}

^fff, fff^, ^fff^ 변형도 각각 시작/종료/둘 다 조건 매치에 대해 True를 반환하지 않아요.


항목 할당 우선순위 (Item assignment)

infix = (항목 할당)

sub infix:<=>(Mu $a is rw, Mu $b)

항목 할당 연산자. 오른쪽의 값을 왼쪽의 Scalar 컨테이너에 복사해요. 같은 기호 =를 쓰지만 우선순위가 더 낮은 리스트 할당 연산자와 구분해야 해요. = 기호 왼쪽의 문맥이 항목 할당으로 파싱될지 리스트 할당으로 파싱될지를 결정해요.

infix => (Pair 생성자)

sub infix:«=>»($key, Mu $value --> Pair:D)

Pair 생성자. 왼쪽을 키, 오른쪽을 값으로 하는 Pair 객체를 만들어요. =>는 구문적으로 특수 처리되어 왼쪽에 따옴표 없는 식별자를 허용해요. 인자 리스트 안에서 왼쪽에 따옴표 없는 식별자가 있는 Pair는 이름 인자로 해석돼요.

my $p = a => 1;
say $p.key;         # OUTPUT: «a␤»
say $p.value;       # OUTPUT: «1␤»

Loose unary 우선순위

prefix not

multi prefix:<not>(Mu $x --> Bool:D)

인자를 Boolean 문맥에서 평가하고(Junction을 붕괴시킴), 결과를 부정해요. not은 오용하기 쉬우니 주의하세요.

prefix so

multi prefix:<so>(Mu $x --> Bool:D)

인자를 Boolean 문맥에서 평가하고 결과를 반환해요.


콤마 연산자 우선순위 (Comma)

infix , (리스트 생성)

sub infix:<,>(*@a --> List:D) is assoc<list>

인자들로 고차 Cool을 구성해요. 첫 경우 List를, 인자가 Pair들이면 Hash를 만들어요.

my @list = :god('Þor'), ['is',"mighty"];
say @list;      # OUTPUT: «[god => Þor [is mighty]]␤»
my %hash = :god('Þor'), :is("mighty");
say %hash.raku; # OUTPUT: «{:god("Þor"), :is("mighty")}␤»

콤마는 호출에서 인자 구분자로도 구문적으로 쓰여요.

infix : (invocant 표시)

중위 ,처럼 인자 구분자로 쓰이면서, 왼쪽 인자를 invocant로 표시해요. 이로써 함수 호출이 메서드 호출로 바뀌어요.

substr('abc': 1);       # 'abc'.substr(1) 과 동일

중위 :는 비메서드 호출의 첫 인자 뒤에서만 허용돼요. 다른 위치에서는 구문 오류예요.


리스트 infix 우선순위 (List infix)

infix Z (zip)

sub infix:<Z>(**@lists --> Seq:D) is assoc<list>

[Zip 연산자]는 지퍼처럼 각 피연산자에서 인덱스가 대응하는 요소를 하나씩 가져와 리스트들을 교차해요. 반환 Seq는 각 피연산자에서 하나씩 값을 갖는 중첩 리스트를 담아요. 한 피연산자가 먼저 소진되면 zip은 멈춰요.

say (1, 2 Z <a b c> Z <+ ->).raku;
# OUTPUT: «((1, "a", "+"), (2, "b", "-")).Seq␤»
for <a b c> Z <1 2 3 4> -> [$l, $r] {
    say "$l:$r"
}
# OUTPUT: «a:1␤b:2␤c:3␤»

Z메타연산자로도 존재하며, 그 경우 내부 리스트들이 연산자를 적용한 값으로 대체돼요.

say 100, 200 Z+ 42, 23;             # OUTPUT: «(142 223)␤»
say 1..3 Z~ <a b c> Z~ 'x' xx 3;    # OUTPUT: «(1ax 2bx 3cx)␤»

infix X (크로스 곱)

multi infix:<X>(+lol, :&with! --> Seq:D)
multi infix:<X>(+lol --> Seq:D)

모든 리스트의 크로스 곱을 만들어 오른쪽 요소가 가장 빠르게 변하도록 정렬하고 Seq를 반환해요.

1..3 X <a b c> X 9
# produces ((1 a 9) (1 b 9) (1 c 9)
#           (2 a 9) (2 b 9) (2 c 9)
#           (3 a 9) (3 b 9) (3 c 9))

X도 메타연산자로 존재해요.

1..3 X~ <a b c> X~ 9
# produces (1a9 1b9 1c9 2a9 2b9 2c9 3a9 3b9 3c9)

infix ... (시퀀스 연산자)

multi infix:<...>(**@) is assoc<list>
multi infix:<...^>(**@) is assoc<list>
multi infix:<^...>(**@) is assoc<list>
multi infix:<^...^>(**@) is assoc<list>

[시퀀스 연산자]는 ... 또는 로 쓸 수 있고, 변형 ...^, ^..., ^...^, …^, ^…, ^…^가 있어요. (아마도 lazy한) 일반 시퀀스를 요청 시 생성해요. 앞에 캐럿이 붙은 변형(^...)은 초기 요소를 포함하지 않는 시퀀스를, 끝에 캐럿이 붙은 변형(...^)은 최종 요소를 포함하지 않는 시퀀스를 만들어요.

왼쪽은 초기 요소를 지정하고, 첫 요소(들) 뒤에 생성기(generator)를 포함할 수 있어요. 오른쪽은 끝점인데, Inf 또는 *(무한·lazy 리스트), True가 되면 시퀀스를 끝내는 표현식, 또는 Junction 등이 될 수 있어요.

끝점 *(Whatever), Inf, 는 기본 생성기 *.succ로 무한 시퀀스를 요청 시 생성해요.

say (1 ... *)[^5];  # OUTPUT: «(1 2 3 4 5)␤»

사용자 정의 생성기는 ... 연산자 앞 리스트의 마지막 요소여야 해요.

say (1, 1, -> $a, $b { $a + $b } ... *)[^8]; # OUTPUT: «(1 1 2 3 5 8 13 21)␤»
say 5, { $_ * 2 } ... 40;                # OUTPUT: «5 10 20 40␤»

끝점이 *가 아니면 각 생성 요소에 대해 스마트매치되고, 스마트매치가 성공하면 시퀀스가 종료돼요. 생성기를 제공하지 않으면 시퀀스 연산자가 시퀀스를 추론해요. 대부분 *.succ 또는 *.pred 기본값을 쓰고, 초기 요소가 여러 개이고 모두 숫자면 산술·기하 시퀀스를 찾아요.

say 1 ... 4;        # OUTPUT: «(1 2 3 4)␤»
say 4 ... 1;        # OUTPUT: «(4 3 2 1)␤»
say 2, 4, 6 ... 12;     # OUTPUT: «(2 4 6 8 10 12)␤»
say 1, 2, 4 ... 32;     # OUTPUT: «(1 2 4 8 16 32)␤»
say 'a' ... 'e';    # OUTPUT: «(a b c d e)␤»

리스트 prefix 우선순위 (List prefix)

infix = (리스트 할당)

리스트 할당 연산자는 일반적으로 오른쪽의 값을 왼쪽 컨테이너로 복사해요. 정확한 의미론은 왼쪽 컨테이너 타입에 맡겨져요. Array, Hash가 일반적인 경우예요. 같은 기호 =를 쓰지만 우선순위가 더 높은 항목 할당 연산자와 구분해야 해요.

infix := (바인딩)

[바인딩 연산자]. $x = $y$y의 값을 $x에 넣는 반면, $x := $y$x$y같은 것이 되게 해요.

my $a = 42;
my $b = $a;
$b++;
say $a;   # 42 — $a와 $b는 다른 컨테이너
my $a = 42;
my $b := $a;
$b++;
say $a;   # 43 — $b와 $a는 같은 객체

변수/컨테이너에 타입 제약이 있으면 런타임에 타입 검사가 수행되고, 실패 시 X::TypeCheck::BindingType이 던져져요. :=는 컴파일 타임 연산자라 런타임에 참조할 수 없고 메타연산자의 인자로 쓰일 수 없어요.

infix ::= (읽기 전용 바인딩)

[읽기 전용 바인딩 연산자]. Rakudo에서 아직 구현되지 않았어요.

listop ... (stub 연산자)

yada, yada, yada 연산자 또는 stub 연산자. 루틴이나 타입에서 유일한 문이면 그 루틴/타입을 스텁으로 표시해요. ... 문이 실행되면 기본 메시지 Stub code executedfail을 호출해요. 클래스의 전방 선언에 쓸 수 있어요.

class Foo {...}
class Bar {
    has Foo $.foo;
}
class Foo {
    say "This is a Foo object";
}

listop !!! / ???

!!! 문이 실행되면 Stub code executed 메시지로 die를, ???warn을 호출해요. 둘 다 스텁 표시용으로 쓰여요.

Reduction 연산자 (환원 연산자)

비결합(non-associating) 연산자를 제외한 모든 중위 연산자는 term 위치에서 대괄호로 둘러싸 그 연산으로 환원하는 listop으로 만들 수 있어요.

say [+] 1, 2, 3;      # 1 + 2 + 3 = 6
my @a = (5, 6);
say [*] @a;           # 5 * 6 = 30

환원 연산자는 기반 연산자와 같은 결합성을 가져요.

say [-] 4, 3, 2;      # 4-3-2 = (4-3)-2 = -1
say [**] 4, 3, 2;     # 4**3**2 = 4**(3**2) = 262144

단일 요소에 [+]를 적용하면 그 요소를 반환해요.


Loose AND / Loose OR 우선순위

and

infix &&와 같지만 우선순위가 더 느슨해요. False로 평가되는 첫 피연산자를 반환하고, 아니면 마지막 피연산자를 반환해요. 단락돼요.

andthen

첫 인자가 정의되지 않았으면 Empty를, 아니면 마지막 인자를 반환해요. 마지막 인자는 정의 검사 없이 그대로 반환돼요. 단락돼요. 왼쪽 결과는 오른쪽의 $_에 바인딩되거나, 오른쪽이 Callable이면 (count가 0 또는 1인) 인자로 전달돼요.

andthen을 쓰면 루틴의 반환 값을 $_에 별칭하고 추가 조작(출력 등)을 할 수 있어요. Failure는 절대 정의되지 않으므로 이 연산자로 처리할 수 있어요.

"does this match?" ~~ /this/ andthen {
    .put;
    my $thing-to-do-if-match-succeeds;
    ...
} # OUTPUT: «this␤»

andthenwith 문 수식어와 가까운 친척이에요.

notandthen

첫 인자가 정의되어 있으면 Empty를, 아니면 마지막 인자를 반환해요. orelse와는 미묘하게 달라요. notandthen은 정의된 항목을 만나면 Empty를 반환하는 반면, orelse는 그 항목을 반환해요. 즉 notandthen은 항목들이 정의되지 않았을 때 행동할 수단이고, orelse는 첫 번째 정의된 항목을 얻는 수단이에요.

sub all-sensors-down     { [notandthen] |@_, True             }
sub first-working-sensor { [orelse]     |@_, 'default sensor' }

all-sensors-down Nil, Nil, Nil
  and say 'OMG! All sensors are down!'; # OUTPUT:«OMG! All sensors are down!␤»
say first-working-sensor Nil, Nil, Nil; # OUTPUT:«default sensor␤»

or

infix ||와 같지만 우선순위가 더 느슨해요. Boolean 문맥에서 True로 평가되는 첫 인자, 아니면 마지막 인자를 반환하고 단락돼요.

orelse

infix //와 비슷하지만 우선순위가 더 느슨하고 $_ 별칭이 있어요. 첫 번째 정의된 인자, 아니면 마지막 인자를 반환해요. 마지막 인자는 정의 검사 없이 그대로 반환돼요. 단락돼요. 루틴들이 반환하는 Failure를 처리하는 데 유용해요(기대값은 보통 정의되고 Failure는 절대 정의되지 않으므로).

sub meows { ++$ < 4 ?? fail 'out of meows!' !! '🐱' }

sub meows-processor1 { meows() orelse .return } # 처리된 Failure 반환
sub meows-processor2 { meows() orelse fail $_ } # 재발화된 Failure 반환
sub meows-processor3 {
    # Failure 아닌 출력을 쓰거나, 문제가 있다는 메시지를 출력
    meows() andthen .say orelse 'something's wrong'.say;
}

say "{.^name}, {.handled}"  # OUTPUT: «Failure, True␤»
    given meows-processor1;
say "{.^name}, {.handled}"  # OUTPUT: «Failure, False␤»
    given meows-processor2;
meows-processor3;           # OUTPUT: «something's wrong␤»
meows-processor3;           # OUTPUT: «🐱␤»

xor

infix ^^와 같지만 우선순위가 더 느슨해요. 다른 피연산자가 false일 때만 true인 피연산자를 반환해요. 둘 다 false면 마지막 인자를, 둘 다 true면 Nil을 반환해요.


시퀀서 우선순위 (Sequencer / 피드 연산자)

infix ==> (오른쪽 피드)

[피드 연산자]는 왼쪽의 결과를 가져와 다음(오른쪽) 루틴의 마지막 매개변수로 전달해요.

my @array = 1, 2, 3, 4, 5;
@array ==> sum() ==> say();   # OUTPUT: «15␤»

이는 say(sum(@array))와 동등해요. 우선순위가 매우 느슨해서 결과를 할당하려면 괄호를 쓰거나 다른 피드 연산자를 쓰면 돼요. 루틴/메서드 체이닝 패턴을 느슨하게 만들고, 병렬 실행 가능성을 염두에 둔 설계예요.

전통적 구조(아래에서 위로 읽기)를 왼쪽→오른쪽 피드로 다시 쓰면:

my @result = (
    <TWO THREE FOUR FIVE SEVEN> »~» " " X~ <FIFTHS SIXTHS EIGHTHS> # (1) 입력
    ==> map( {"VULGAR FRACTION " ~ $^þ } )                         # (2) Unicode 이름으로 변환
    ==> grep({ .uniparse })                                        # (3) 실제 이름만 필터
    ==> map( { .uniparse} );                                       # (4) Unicode로 변환
);

괄호 없이 할당하려면 다른 피드 연산자를 쓰면 돼요.

<people of earth>
    ==> map({ .tc })
    ==> grep /<[PE]>/
    ==> sort()
    ==> my @result;

infix <== (왼쪽 피드)

[왼쪽 피드 연산자]는 오른쪽의 결과를 가져와 이전(왼쪽) 루틴의 마지막 매개변수로 전달해요. 오른쪽에서 왼쪽으로의 데이터 흐름을 분명히 보여줘요.

my @result
    <== sort()              # (4) 정렬, 결과는 <Earth People>
    <== grep({ /<[PE]>/ })  # (3) P 또는 E 찾기
    <== map({ .tc })        # (2) 단어 대문자화
    <== <people of earth>;  # (1) 입력으로 시작

정체성 (Identity)

일반적으로 중위 연산자는 단일 요소나 요소 없음에 오류 없이 적용될 수 있고, 대개 reduce 연산 문맥에서 그래요.

say [-] ()  # OUTPUT: «0␤»

설계 문서는 이것이 정체성 값을 반환해야 하고, 모든 연산자에 정체성 값이 지정되어야 한다고 명시해요. Raku에서 연산자 클래스별로 정의된 방식:

연산자 클래스 정체성 값
Equality True
Arithmetic + 0
Arithmetic * 1
Comparison True
Bitwise 0
Stringy ''
Sets 빈 집합 또는 동등
Or-like Bool False
And-like Bool True

예를 들어 빈 리스트의 합집합은 빈 집합을 반환해요.

say [∪];  # OUTPUT: «Set()␤»

이는 빈 값이나 0이 항상 유효한 피연산자인 연산자에만 적용돼요. 나눗셈에 적용하면 예외가 발생해요.

say [%] ();  # OUTPUT: «(exit code 1) No zero-arg meaning for infix:<%>␤»