구문(Syntax)
구문(Syntax) (syntax)
Raku는 인간 언어에서 많은 개념을 빌려 왔어요. 언어학자가 설계했으니 그리 놀랍지 않죠. Raku는 같은 요소를 다른 맥락에서 재사용해요. 명사(terms)와 동사(operators)라는 관념이 있고, 문맥에 따라 다르게 해석되는데, 그래서 어떤 기호가 명사가 기대되는 자리인지 동사가 기대되는 자리인지에 따라 의미가 달라질 수 있어요. 또한 자기 클로킹(self-clocking)이라서, 파서가 흔한 오류 대부분을 잡아내고 좋은 오류 메시지를 줄 수 있어요.
어휘 규칙(Lexical conventions)
Raku 코드는 Unicode 텍스트예요. 현재 구현들은 입력 인코딩으로 UTF-8을 지원해요. Unicode 대 ASCII 기호도 함께 보세요.
자유 형식(Free form)
Raku 코드는 자유 형식이에요. 공백을 얼마나 쓰든 대체로 자유롭죠. 다만 어떤 경우에는 공백의 유무가 의미를 담당하기도 해요. 그래서 이렇게 쓸 수도 있고
if True {
say "Hello";
}
이렇게 쓸 수도 있어요.
if True {
say "Hello"; # Bad indentation intended
}
이렇게도, 심지어 이렇게까지도요.
if True { say "Hello" }
if True {say "Hello"}
다만 마지막 예시에서는 더 이상 공백을 뺄 수 없어요.
언스페이스(Unspace)
컴파일러가 공백을 허용하지 않는 자리에서도, 백슬래시로 인용하면 (백스페이스를) 얼마든지 쓸 수 있어요. 다만 토큰 안의 언스페이스는 지원되지 않아요. 언스페이스된 줄바꿈은 컴파일러가 줄 번호를 만들 때 여전히 계산돼요. 언스페이스의 용도는 후위 연산자와 루틴 인자 목록을 분리하는 거예요.
sub alignment(+@l) { +@l };
sub long-name-alignment(+@l) { +@l };
alignment\ (1,2,3,4).say;
long-name-alignment(3,5)\ .say;
say Inf+Inf\i;
이 예에서 우리 의도는 두 문장의 .과 괄호를 정렬하는 거였어요. 그래서 패딩으로 쓰는 공백 앞에 \를 붙였죠.
세미콜론으로 문장 구분하기
Raku 프로그램은 세미콜론 ;으로 구분된 문장들의 목록이에요.
say "Hello";
say "world";
마지막 문장(또는 블록 안의 마지막 문장) 뒤의 세미콜론은 선택사항이에요.
say "Hello";
say "world"
if True {
say "Hello"
}
say "world"
암시적 구분자 규칙(Implied separator rule)
한 줄의 마지막 코드(줄 끝 주석 제외)가 블록이나 해시 생성자 리터럴의 닫는 중괄호이고, 그것을 구문적으로 문장의 끝으로 해석할 수 있다면, 끝의 세미콜론은 선택사항이에요(보통 생략함). 예를 들어 중괄호 리터럴 해시를 변수에 할당한 뒤나, 아래처럼 if 문 블록 뒤에는 세미콜론을 쓸 필요가 없어요.
my $hash = { :foo, :bar }
if True { say "Hello" }
say "world";
하지만 블록과 같은 줄의 뒤따르는 문장을 분리하려면 세미콜론이 필요해요.
if True { say "Hello" }; say "world";
# ^^^ this ; is required
이 암시적 문장 구분자 규칙은 제어문 외에도 다른 방식으로 적용돼요. 예를 들어 메서드 호출의 콜론 : 문법과 함께 쓰여요.
my @names = <Foo Bar Baz>;
my @uppercase-names = @names.map: { .uc } # OUTPUT: [FOO BAR BAZ]
같은 if/elsif/else(또는 비슷한) 구조의 일부인 일련의 블록에서는, 암시적 규칙이 그 계열의 마지막 블록 끝에만 적용돼요. 아래 세 가지는 동등해요.
if True { say "Hello" } else { say "Goodbye" }; say "world";
# ^^^ this ; is required
if True { say "Hello" } else { say "Goodbye" } # <- implied statement separator
say "world";
if True { say "Hello" } # still in the middle of an if/else statement
else { say "Goodbye" } # <- no semicolon required because it ends in a block
# without trailing statements in the same line
say "world";
주석(Comments)
주석은 오직 사람이 읽기 위한 프로그램 텍스트 부분이에요. Raku 컴파일러는 주석을 프로그램 텍스트로 평가하지 않아요. 주석은 Pod6 텍스트를 포함하는 비주변적(non-ambient) 코드의 일부죠. 공백의 유무가 파싱을 갈라놓는 자리에서 주석은 공백으로 취급돼요.
한 줄 주석
Raku에서 가장 흔한 주석 형태는 해시 문자 # 하나로 시작해 줄 끝까지 가는 것이에요.
if $age > 250 { # catch obvious outliers
# this is another comment!
die "That doesn't look right"
}
여러 줄 / 인라인 주석
여러 줄 및 인라인 주석은 해시 문자, 그 다음 백틱, 그 다음 Unicode Open Punctuation 문자로 시작하고, 짝이 맞는 Close Punctuation 문자로 끝나요. 백틱과 구두점 문자 사이에는 공백이 허용되지 않아요. (공백이 있으면 한 줄 주석으로 취급돼요.) 내용은 여러 줄에 걸칠 수 있을 뿐 아니라 인라인으로도 삽입할 수 있어요.
if #`( why would I ever write an inline comment here? ) True {
say "something stupid";
}
이 주석은 여러 줄로 확장될 수 있어요.
#`[
And this is how a multi would work.
That says why we do what we do below.
]
say "No more";
주석 안의 중괄호는 중첩될 수 있어요. #{ a { b } c }에서는 주석이 문자열 맨 끝까지 갑니다. 그래서 주석 본문에 여는 괄호 문자가 나타나면(예: #[ This is a box [ of stuff ] ]) 짝이 되는 닫는 문자를 갖춰야 해요. 여러 중괄호도 쓸 수 있어요. #{{ double-curly-brace }}처럼요. 중첩 구분자와 구별하는 데 도움이 될 수 있어요. 키워드나 식별자 한가운데에만 넣지 않는다면, 이 주석을 표현식 안에 삽입할 수 있어요.
Pod 주석
여러 줄 주석에는 Pod 문법을 쓸 수 있어요.
say "this is code";
=begin comment
Here are several
lines
of comment
=end comment
say 'code again';
식별자(Identifiers)
식별자는 상수·변수(예: Scalar)·루틴(예: Sub과 메서드) 같은 엔티티·객체에 이름을 붙이는 데 쓸 수 있는 문법 구성 요소예요. 변수 이름에서는 어떤 시길(과 트위길)이라도 식별자보다 앞에 오고, 식별자의 일부가 되지 않아요.
constant c = 299792458; # identifier "c" names an Int
my $a = 123; # identifier "a" in the name "$a" of a Scalar
sub hello { say "Hello!" }; # identifier "hello" names a Sub
식별자는 보통 식별자, 확장 식별자, 복합 식별자 등 다양한 형태로 나와요.
보통 식별자(Ordinary identifiers)
보통 식별자는 선두 알파벳 문자로 이루어지고, 하나 이상의 영숫자 문자가 이어질 수 있어요. 고립되고 내장된 아포스트로피 '나 하이픈 -도 포함할 수 있는데, 다음 문자가 매번 알파벳이어야 해요.
"알파벳"과 "영숫자"의 정의는 적절한 Unicode 문자를 포함해요. 어떤 문자가 "적절한지"는 구현에 따라 달라요. Rakudo/MoarVM Raku 구현에서 알파벳 문자는 Unicode 일반 범주 값 Letter(L)인 문자와 밑줄 _을 포함해요. 영숫자 문자는 추가로 Unicode 일반 범주 값 Number, Decimal Digit(Nd)인 문자를 포함하죠.
# valid ordinary identifiers:
x
_snake_oil
something-longer
with-numbers1234
don't-do-that
piece_of_π
駱駝道 # "Rakuda-dō", Japanese for "Way of the camel"
# invalid ordinary identifiers:
42 # identifier does not start with alphabetic character
with-numbers1234-5 # embedded hyphen not followed by alphabetic character
is-prime? # question mark is not alphanumeric
x² # superscript 2 is not alphanumeric (explained above)
확장 식별자(Extended identifiers)
보통 식별자에서 허용되지 않는 문자를 이름에 포함하는 것이 편리할 때가 있어요. 엔티티 집합이 공통의 "짧은" 이름을 공유하면서도 각각 요소를 개별적으로 식별할 필요가 있는 상황이 그 예죠. 예를 들어 짧은 이름이 Dog인 모듈을 쓰는데, 긴 이름이 명명 기관과 버전을 포함한다고 해 볼게요.
Dog:auth<Somebody>:ver<1.0> # long module names including author and version
Dog:auth<Somebody>:ver<2.0>
use Dog:auth<Somebody>:ver<2.0>;
# Selection of second module causes its full name to be aliased to the
# short name for the rest of # the lexical scope, allowing a declaration
# like this.
my Dog $spot .= new("woof");
비슷하게, 연산자 집합은 prefix, infix, postfix 같은 이름으로 여러 구문 범주에서 함께 동작해요. 그 연산자들의 공식 이름은 보통 보통 식별자에서 제외된 문자를 포함해요. 긴 이름이 곧 확장 식별자를 이루고 이 구문 범주를 포함해요. 짧은 이름은 정의에서 인용 부호 안에 들어가죠.
infix:<+> # the official name of the operator in $a + $b
infix:<*> # the official name of the operator in $a * $b
infix:«<=» # the official name of the operator in $a <= $b
이런 모든 용도에서, 하나 이상의 콜론으로 구분된 문자열을 보통 식별자에 덧붙여 소위 _확장 식별자_를 만들 수 있어요. 식별자에 덧붙여지면(즉 후위 위치에서) 이 콜론으로 구분된 문자열은 그 식별자의 고유한 변형을 만들어 내요.
그 문자열은 :key<value> 형태를 가지는데, 여기서 key 또는 value는 선택사항이에요. 즉 보통 식별자와 구분하는 콜론 뒤에는 key 및/또는 < >, « », [' '] 같은 인용 괄호 구조가 오는데, 그것은 하나 이상의 임의 문자 value를 인용해요.[1]
# exemplary valid extended identifiers:
postfix:<²> # the official long name of the operator in $x²
WOW:That'sAwesome
WOW:That's<<🆒>>
party:sweet<16>
# exemplary invalid extended identifiers:
party:16<sweet> # 16 is not an ordinary identifier
party:16sweet
party:!a # ...and neither is !a
party:$a # ...nor $a
확장 식별자에서 후위 문자열은 이름의 필수 일부로 취급돼요. 그래서 infix:<+>와 infix:<->는 서로 다른 연산자예요. 다만 사용된 괄호 문자는 이름의 일부로 세지 않아요. 인용된 데이터만 중요하죠. 그래서 이 이름들은 모두 같아요.
infix:<+>
infix:<<+>>
infix:«+»
infix:['+']
infix:('+')
비슷하게 이 모든 것도 동작해요.
my $foo:bar<baz> = 'quux';
say $foo:bar«baz»; # OUTPUT: «quux»
my $take-me:<home> = 'Where the glory has no end';
say $take-me:['home']; # OUTPUT: «Where [...]»
my $foo:bar<2> = 5;
say $foo:bar(1+1); # OUTPUT: «5»
확장 식별자가 콜론페어를 둘 이상 갖는다면, 그 순서는 일반적으로 중요해요.
my $a:b<c>:d<e> = 100;
my $a:d<e>:b<c> = 200;
say $a:b<c>:d<e>; # OUTPUT: «100», NOT: «200»
이 규칙의 예외는 모듈 버전 지정 이에요. 그래서 이 식별자들은 사실상 같은 모듈을 가리켜요.
use ThatModule:auth<Somebody>:ver<2.7.18.28.18>
use ThatModule:ver<2.7.18.28.18>:auth<Somebody>
게다가 확장 식별자는 컴파일 타임 보간을 지원해요. 보간 값에는 constants를 사용해야 해요.
constant $c = 42; # Constant binds to Int; $-sigil enables interpolation
my $a:foo<42> = "answer";
say $a:foo«$c»; # OUTPUT: «answer»
인용 괄호 구조는 식별자 맥락에서 일반적으로 서로 바꿔 쓸 수 있지만, 동일하지는 않아요. 특히 작은 꺽쇠 < >(단일 인용 보간 특성을 모방함)는 상수 이름의 보간에 쓸 수 없어요.
constant $what = 'are';
my @we:<are>= <the champions>;
say @we:«$what»; # OUTPUT: «[the champions]»
say @we:<$what>;
# Compilation error: Variable '@we:<$what>' is not declared
복합 식별자(Compound identifiers)
복합 식별자는 이중 콜론 ::으로 서로 구분된 둘 이상의 보통 및/또는 확장 식별자로 이루어진 식별자예요. 이중 콜론 ::은 네임스페이스 구분자 또는 _패키지 구분자_로 알려져 있어요. 이름에서 이중 콜론의 의미적 기능은 이름의 앞부분을 패키지/네임스페이스로 간주하고, 그 뒤의 이름 부분이 그 안에서 찾아지도록 하는 거예요.
module MyModule { # declare a module package
our $var = "Hello"; # declare package-scoped variable
}
say $MyModule::var # OUTPUT: «Hello»
위 예에서 MyModule::var는 패키지 이름 식별자 MyModule과 변수 이름의 식별자 부분 var로 이루어진 복합 식별자예요. 전체 $MyModule::var는 흔히 패키지 한정 이름이라고 불러요.
이중 콜론으로 식별자를 구분하면 맨 오른쪽 이름이 기존(위 예 참고) 또는 자동 생성된 패키지에 삽입돼요.
my $foo::bar = 1;
say OUR::.keys; # OUTPUT: «(foo)»
say OUR::foo.HOW # OUTPUT: «Perl6::Metamodel::PackageHOW.new»
마지막 줄은 foo 패키지가 그 네임스페이스의 변수를 위한 저장소로 자동 생성되었음을 보여줘요.
이중 콜론 문법은 보통 패키지나 변수 이름을 넣는 자리에 ::($expr)를 써서 문자열을 패키지·변수 이름으로 런타임에 보간할 수 있게 해줘요.
my $buz = "quux";
my $bur::quux = 7;
say $bur::($buz); # OUTPUT: «7»
term term:<>
term:<>로 새 term을 도입할 수 있는데, 보통 식별자 규칙을 벗어나는 상수를 도입할 때 유용해요.
use Test; plan 1; constant &term:<👍> = &ok.assuming(True);
👍
# OUTPUT: «1..1ok 1 - »
하지만 term이 상수일 필요는 없어요. 인자를 받지 않는 함수에도 쓸 수 있고, 파서가 그 뒤에 연산자가 오기를 기대하도록 강제할 수도 있어요. 예를 들어
sub term:<dice> { (1..6).pick };
say dice + dice;
는 2와 12 사이의 어떤 숫자도 출력할 수 있어요. 만약 dice를 보통의
sub dice() {(1...6).pick }
으로 선언했다면, dice + dice라는 표현식은 dice(+(dice()))로 파싱되어, sub dice가 인자를 0개 기대하므로 오류가 날 거예요.
문장과 표현식(Statements and expressions)
Raku 프로그램은 문장들의 목록으로 만들어져요. 문장의 특별한 경우는 값을 돌려주는 _표현식(expression)_이에요. 예를 들어 if True { say 42 }는 구문상 문장이지만 표현식은 아니에요. 반면 1 + 2는 표현식이죠(그래서 역시 문장이기도 함).
do 접두사는 문장을 표현식으로 바꿔요. 그래서
my $x = if True { 42 }; # Syntax error!
는 오류지만,
my $x = do if True { 42 };
는 if 문의 반환 값(여기서는 42)을 변수 $x에 할당해요.
Terms
Terms는 기본 명사로, 선택적으로 연산자와 함께 표현식을 이룰 수 있어요. term의 예시로는 변수($x), 타입 이름(Int) 같은 bareword, 리터럴(42), 선언(sub f() { }), 호출(f())이 있어요. 예를 들어 2 * $salary에서 2와 $salary는 두 term이에요(Int 리터럴과 변수).
변수(Variables)
변수는 보통 _시길(sigil)_이라는 특수 문자로 시작하고, 식별자가 이어져요. 변수는 사용하기 전에 선언해야 해요.
# declaration:
my $number = 21;
# usage:
say $number * 2;
자세한 내용은 변수 문서를 참고하세요.
Bareword(상수, 타입 이름)
미리 선언된 식별자는 그 자체로 term이 될 수 있어요. 보통 타입 이름이나 상수지만, 메서드가 호출된 객체를 가리키는 term self(참고: objects)와 시길 없는 변수도 있어요.
say Int; # OUTPUT: «(Int)»
# ^^^ type name (built in)
constant answer = 42;
say answer;
# ^^^^^^ constant
class Foo {
method type-name {
self.^name;
# ^^^^ built-in term 'self'
}
}
say Foo.type-name; # OUTPUT: «Foo»
# ^^^ type name
패키지와 한정 이름(Packages and qualified names)
변수·상수·클래스·모듈·sub 같은 이름 붙은 엔티티는 네임스페이스의 일부예요. 이름의 중첩된 부분은 ::으로 계층을 구분해요. 몇 가지 예시를 볼게요.
$foo # simple identifiers
$Foo::Bar::baz # compound identifiers separated by ::
$Foo::($bar)::baz # compound identifiers that perform interpolations
Foo::Bar::bob(23) # function invocation given qualified name
자세한 내용은 패키지 문서를 참고하세요.
리터럴(Literals)
리터럴은 소스 코드에서 상수 값을 표현한 것이에요. Raku에는 Str, 여러 숫자 타입, Pair 등 내장 타입 몇 가지에 대한 리터럴이 있어요.
문자열 리터럴
문자열 리터럴은 인용 부호로 둘러싸여요.
say 'a string literal';
say "a string literal\nthat interprets escape sequences";
인용에서 더 많은 옵션을 볼 수 있어요. 보간 인용 qq을 포함해서요. Raku는 리터럴에서 표준 이스케이프 문자 \0 \a \b \t \n \f \r \e를 사용하며, 그 의미는 설계 문서에 명시된 ASCII 이스케이프 코드와 같아요.
say "🔔\a"; # OUTPUT: «🔔␇»
숫자 리터럴
숫자 리터럴은 일반적으로 10진수로 지정돼요(필요하면 0d 접두사로 명시할 수 있음). 0x(hexadecimal, 16진수), 0o(octal, 8진수), 0b(binary, 2진수) 같은 접두사나 :16<A0> 같은 부사 표기의 명시적 진법이 아니면요. 다른 언어와 달리, 선두 0은 8진수를 나타내지 않아요. 대신 컴파일 타임 경고가 나오죠.
모든 리터럴 형식에서 밑줄로 숫자를 묶을 수 있지만, 의미 정보는 없어요. 다음 리터럴들은 모두 같은 숫자로 평가돼요.
1000000
1_000_000
10_00000
100_00_00
Int 리터럴
정수는 기본적으로 부호 있는 10진수지만, 다른 진법도 쓸 수 있어요. 자세한 내용은 Int를 보세요.
# not a single literal, but unary - operator applied to numeric literal 2
-2
12345
0xBEEF # base 16
0o755 # base 8
:3<1201> # arbitrary base, here base 3
Rat 리터럴
Rat 리터럴(유리수)은 아주 흔하고, 다른 많은 언어에서 소수나 부동소수가 쓰이는 자리를 대신해요. 정수 나눗셈도 Rat을 만들어요.
1. # Error: A number must have at least one digit after the radix point
1.0
3.14159
-2.5 # Not actually a literal, but still a Rat
:3<21.0012> # Base 3 rational
⅔
2/3 # Not actually a literal, but still a Rat
Num 리터럴
e 뒤에 10진수 정수 지수가 오는 과학적 표기법은 Num(부동소수점 값)을 만들어요.
1.e0 # error: A number must have at least one digit after the radix point
1e0
6.022e23
1e-9
-2e48
2e2.5 # error
Complex 리터럴
Complex 수는 허수(후위 i가 붙은 유리수)로 쓰거나, 실수와 허수의 합으로 써요.
1.+2i # error: A number must have at least one digit after the radix point
1+2.i # error: A number must have at least one digit after the radix point
1+2i
6.123e5i # note that this is 6.123e5 * i, not 6.123 * 10 ** (5i)
Pair 리터럴
Pair은 키와 값으로 이루어지고, key => 'value'와 :key('value') 두 기본 형태로 만들 수 있어요.
화살표 페어(Arrow pairs)
화살표 페어는 표현식, 문자열 리터럴, 또는 "bare identifier"를 가질 수 있어요. bare identifier는 왼쪽에 인용 부호가 필요 없는 보통 식별자 문법의 문자열이에요.
like-an-identifier-ain't-it => 42
"key" => 42
('a' ~ 'b') => 1
부사 페어(Adverbial pairs, 콜론 페어)
명시적 값이 없는 짧은 형태:
my $thing = 42;
:$thing # same as thing => $thing
:thing # same as thing => True
:!thing # same as thing => False
변수 형태는 :&callback이나 :@elements처럼 다른 시길에서도 동작해요. 값이 숫자 리터럴이면 이 짧은 형태로도 표현할 수 있어요.
:42thing # same as thing => 42
:٤٢thing # same as thing => 42
다른 알파벳을 쓰면 순서가 뒤집혀요.
:٤٢ث # same as ث => ٤٢
thaa 문자가 숫자보다 앞에 오죠.
명시적 값이 있는 긴 형태:
:thing($value) # same as thing => $value
:thing<quoted list> # same as thing => <quoted list>
:thing['some', 'values'] # same as thing => ['some', 'values']
:thing{a => 'b'} # same as thing => { a => 'b' }
Boolean 리터럴
True와 False는 Boolean 리터럴이에요. 항상 대문자로 시작해요.
배열 리터럴
대괄호 한 쌍이 표현식을 둘러싸 항목화된 Array 리터럴을 만들 수 있어요. 보통 안에는 쉼표로 구분된 목록이 있어요.
say ['a', 'b', 42].join(' '); # OUTPUT: «a b 42»
# ^^^^^^^^^^^^^^ Array constructor
생성자에 단일 Iterable이 주어지면 이를 복제하고 평탄화해요. Iterable인 요소 딱 하나를 가진 Array를 원한다면, 뒤에 쉼표를 붙여야 해요.
my @a = 1, 2;
say [@a].raku; # OUTPUT: «[1, 2]»
say [@a,].raku; # OUTPUT: «[[1, 2],]»
Array 생성자는 다른 타입의 내용은 평탄화하지 않아요. 필요한 항목을 평탄화하려면 Slip 접두 연산자(|)를 쓰세요.
my @a = 1, 2;
say [@a, 3, 4].raku; # OUTPUT: «[[1, 2], 3, 4]»
say [|@a, 3, 4].raku; # OUTPUT: «[1, 2, 3, 4]»
List 타입은 Array로부터의 변환 없이 배열 리터럴 선언에서 명시적으로 만들 수 있어요. 선언에서 is trait을 쓰면 되죠.
my @a is List = 1, 2; # a List, not an Array
# wrong: creates an Array of Lists
my List @a;
해시 리터럴
선두 연관 시길과 괄호 쌍 %( )이 Pair들의 List를 둘러싸 Hash 리터럴을 만들 수 있어요. 보통 안에는 쉼표로 구분된 Pair 목록이 있어요. 페어가 아닌 것이 쓰이면 키로 간주되고, 다음 요소가 값이 돼요. 대개 단순한 화살표 페어와 함께 쓰죠.
say %( a => 3, b => 23, :foo, :dog<cat>, "french", "fries" );
# OUTPUT: «a => 3, b => 23, dog => cat, foo => True, french => fries»
say %(a => 73, foo => "fish").keys.join(" "); # OUTPUT: «a foo»
# ^^^^^^^^^^^^^^^^^^^^^^^^^ Hash constructor
왼쪽의 %-시길 변수에 할당할 때, 오른쪽 Pair를 둘러싼 시길과 괄호는 선택사항이에요.
my %ages = fred => 23, jean => 87, ann => 4;
기본적으로 %( )의 키는 문자열로 강제돼요. 문자열이 아닌 키로 해시를 구성하려면 콜론 접두사가 있는 중괄호 구분자 :{ }를 쓰세요.
my $when = :{ (now) => "Instant", (DateTime.now) => "DateTime" };
객체가 키일 때, 문자열이 아닌 키를 문자열로 접근할 수 없다는 점을 주의하세요.
say :{ -1 => 41, 0 => 42, 1 => 43 }<0>; # OUTPUT: «(Any)»
say :{ -1 => 41, 0 => 42, 1 => 43 }{0}; # OUTPUT: «42»
Associative 역할을 구현하는 특정 타입, 즉 Map(Hash와 Stash 하위 클래스 포함)과 QuantHash(그 하위 클래스 포함)은 변환 없이 해시 리터럴에서 명시적으로 만들 수 있어요. 선언에서 is trait을 쓰면 되죠.
my %hash; # Hash
my %hash is Hash; # explicit Hash
my %map is Map; # Map
my %stash is Stash; # Stash
my %quant-hash is QuantHash; # QuantHash
my %setty is Setty; # Setty
my %set is Set; # Set
my %set-hash is SetHash; # SetHash
my %baggy is Baggy; # Baggy
my %bag is Bag; # Bag
my %bag-hash is BagHash; # BagHash
my %mixy is Mixy; # Mixy
my %mix is Mix; # Mix
my %mix-hash is MixHash; # MixHash
해시 시길로 일반 타입 선언을 쓰면 특정 타입이 아니라 타입화된 Hash가 만들어진다는 점을 주의하세요.
# This is wrong: creates a Hash of Mixes, not Mix:
my Mix %mix;
# Works with $ sigil:
my Mix $mix;
# Can be typed:
my Mix[Int] $mix-of-ints;
정규식 리터럴
Regex는 /foo/처럼 슬래시로 선언해요. 이 // 문법은 전체 rx// 문법의 줄임말이에요.
/foo/ # Short version
rx/foo/ # Longer version
Q :regex /foo/ # Even longer version
my $r = /foo/; # Regexes can be assigned to variables
시그니처 리터럴
시그니처는 sub와 블록 선언에서의 전형적인 용도 외에도, 패턴 매칭을 위해 단독으로 쓸 수 있어요. 단독 시그니처는 콜론으로 시작해 선언돼요.
say "match!" if 5, "fish" ~~ :(Int, Str); # OUTPUT: «match!»
my $sig = :(Int $a, Str);
say "match!" if (5, "fish") ~~ $sig; # OUTPUT: «match!»
given "foo", 42 {
when :(Str, Str) { "This won't match" }
when :(Str, Int $n where $n > 20) { "This will!" }
}
자세한 내용은 시그니처 문서를 참고하세요.
선언(Declarations)
변수 선언
my $x; # simple lexical variable
my $x = 7; # initialize the variable
my Int $x = 7; # declare the type
my Int:D $x = 7; # specify that the value must be defined (not undef)
my Int $x where { $_ > 3 } = 7; # constrain the value based on a function
my Int $x where * > 3 = 7; # same constraint, but using Whatever shorthand
다른 스코프(our, has)에 대한 자세한 내용은 변수 선언자와 스코프를 참고하세요.
호출 가능 선언(Callable declarations)
Raku는 여러 Callable 코드 객체(즉 sub루틴처럼 호출할 수 있는 코드 객체)에 대한 문법을 제공해요. 구체적으로 sub루틴(단일·다중 디스패치 모두), 코드 블록, 메서드(역시 단일·다중 디스패치 모두)에 대한 문법이 있어요.
서브루틴 선언
서브루틴은 키워드 sub 뒤에 선택적 이름, 선택적 시그니처, 코드 블록으로 만들어요. 서브루틴은 어휘적으로 스코프되므로, 선언 시점에 이름이 지정되면 같은 이름을 어휘 스코프에서 서브루틴 호출에 쓸 수 있어요. 서브루틴은 Sub 타입의 인스턴스이고 어떤 컨테이너에도 할당할 수 있어요.
sub {}
# The minimal legal subroutine declaration
sub say-hello1 { say "Hello!" }
# A subroutine with a name
sub say-hello2(Str $to-whom) { say "Hello $to-whom!" }
# A subroutine with a name and a signature
서브루틴을 변수에 할당할 수 있어요.
my &greet0 = sub { say "Hello!" } # Unnamed sub assigned to &greet0
my &greet1 = sub say-hello1 { say "Hello!" } # Named sub assigned to &greet1
다중 디스패치 서브루틴 선언
서브루틴은 multi로 선언할 수 있어요. 즉 각기 다른 시그니처를 가진 후보 여러 개를 가진 서브루틴이죠. 다중 디스패치 서브루틴은 단일 디스패치 서브루틴과 거의 똑같이 만들어요. 키워드 multi, 선택적으로 키워드 sub, 선택적 이름, 선택적 시그니처, 코드 블록이 이어져요. 자세한 내용은 Multi-dispatch를 참고하세요.
위에서 정의한 두 단일 디스패치 서브루틴 greet0과 greet1은 다음과 같이 단일 greet multi로 합칠 수 있어요.
multi greet { say "Hello!" }
# ^^^ optional
multi greet($name) { say "Hello $name!" }
multi 선언 앞에는 선택적으로 proto 선언을 둘 수 있어요. proto는 후보로 디스패치되기 전에 어떤 호출이든 반드시 지켜야 하는 시그니처를 선언해요. 예를 들어 이 proto는 모든 후보가 최소 하나의 위치 파라미터를 받아야 한다고 요구해요.
proto at-least1($, |) {*}
multi at-least1($first) { note $first }
multi at-least1($first, $second) { note "got two" }
multi at-least1($first, :$named) { note "got named" }
# The following is legal to *declare* but can never be called
multi at-least1 { note "Got none"}
블록 선언
Block은 서브루틴과 비슷하지만 소규모 코드 재사용을 위한 호출 가능 코드 객체예요. 블록은 { } 중괄호 안의 코드 블록으로 만들어지고, 선택적으로 -> 뒤에 시그니처(또는 rw(읽기-쓰기) 블록의 경우 <-> 뒤에 시그니처)가 올 수 있어요. 시그니처가 없으면 블록은 ^ 트위길과 : 트위길의 자리표시자 변수로 파라미터를 암시적으로 받을 수 있어요. 명시적·암시적 시그니처가 모두 없는 블록은 $_라는 기본 시그니처를 가져요.
my &greet0 = { say "Hello!" } # Block stored in &greet0
my &greet1 = { say "Hello $_!" } # Block with default signature
my &greet2 = -> $to-whom { say "Hello $to-whom!" } # Block with explicit signature
my &greet3 = { say "Hello $^to-whom!" } # Block with implicit positional
my &greet4 = { say "Hello $:to-whom!" } # Block with implicit named
my &add1-in-place = <-> $a { ++$a } # Block with rw signature
덜 관용적이긴 하지만, 블록을 스칼라 변수에 저장할 수도 있어요.
my $greet = { say "Hello!" }
메서드 선언
Method은 특정 객체(메서드의 "invocant")에 대해 호출되는 호출 가능 코드 객체예요. 클래스 선언 밖에서는 메서드가 키워드 method, 시그니처, 코드 블록 순으로 선언돼요. 메서드 시그니처 안에서 메서드의 invocant는 인자를 보통 구분하는 , 대신 :가 뒤따라요. 클래스 밖에서 선언된 메서드는 사용하려면 변수에 저장해야 해요.
my &m = method ($invocant: $arg1, $arg2) { note }
이 문법은 특이해요. 보통 메서드는 클래스 안에서 선언되거든요. 그 맥락에서는 메서드 선언이 서브루틴 선언에 더 가까워요. 메서드는 키워드 method, 이름, 선택적 시그니처, 코드 블록 순으로 선언돼요. 클래스 안에 정의된 메서드는 그 클래스를 기본 invocant로 가지지만, (예를 들어 invocant의 정의성을 제약하기 위해) 기본값을 재정의할 수 있어요.
class Greeter {
method greet($to-whom) { say "Hello $to-whom!" }
method defined-greet(Greeter:D:) { say "Hello!" }
}
메서드에 대한 자세한 내용은 Methods를 참고하세요.
다중 디스패치 메서드 선언
클래스 안에서 다중 디스패치 서브루틴 문법과 매우 유사한 문법으로 다중 디스패치 메서드를 선언할 수 있어요. multi 서브루틴과 마찬가지로, 다중 디스패치 메서드에 대해 proto를 선언할 수도 있어요.
class Greeter {
multi method greet { say "Hello!" }
multi method greet($to-whom) { say "Hello $to-whom!" }
proto method at-least1($, |) {*}
multi method at-least1($first) { note $first }
# The following is legal to *declare* but can never be called
multi method at-least1 { note "Got none"}
}
package, module, class, role, grammar 선언
여러 종류의 패키지가 있고, 각각 키워드, 이름, 선택적 트레잇, 서브루틴·메서드·규칙의 본문으로 선언돼요.
package P { }
module M { }
class C { }
role R { }
grammar G { }
하나의 파일에 여러 패키지를 선언할 수 있어요. 하지만 파일 시작에 unit 패키지를 선언하면, 파일의 나머지가 패키지의 본문으로 취급돼요. 이 경우 중괄호는 필요 없어요.
unit module M;
# ... stuff goes here instead of in {}'s
코드 객체 호출(Invoking code objects)
Raku는 서브루틴/블록 호출과 메서드 호출을 위한 표준 문법을 제공해요. 게다가 서브루틴을 메서드처럼 호출하고 메서드를 서브루틴처럼 호출하는 대체 문법도 제공해요.
서브루틴 또는 블록 호출
어휘적으로 선언된 서브루틴과 &-시길 변수에 할당된 서브루틴/블록은 이름 뒤에 인자(선택적으로 (…)로 감쌈)를 붙여 호출할 수 있어요. 또는 이름 앞에 & 시길을 붙일 수 있는데, 그 경우 함수의 인자를 감싸는 (…)가 필수고 선택적으로 .이 앞에 올 수 있어요. ((…)가 없는 &-시길 변수는 호출 없이 함수를 객체로 가리켜요.) 그래서 다음 모두가 인자 없이 함수 foo를 호출해요.
foo;
foo();
&foo();
&foo.();
서브루틴이나 블록이 $-시길 변수에 할당되어 있다면, 괄호로만 호출할 수 있어요.
my $foo = sub { note }
$foo();
$foo.();
서브루틴/블록 호출에 대한 자세한 내용은 functions를 참고하세요.
메서드 호출
클래스에 정의된 메서드는 클래스 뒤에 . 그리고 메서드 이름을 붙여 그 클래스의 인스턴스에서 호출해요. 인자 없이 메서드를 호출할 때는 괄호가 필요 없어요. 인자를 공급하려면 (…)로 감싸거나 메서드 이름 뒤에 :을 붙여야 해요. 아래 코드가 위에서 설명한 문법을 보여줘요.
class Person {
has $.age = 0;
has Str $.name = "Anon";
multi method greet { say "Hello, $.name()!" }
multi method greet($name) { say "Hello, $name!" }
};
my $person = Person.new(:name<Jane>, :age(98));
$person.greet; # Calls greet method with 0 args
$person.greet(); # Calls greet method with 0 args
$person.greet('Nushi'); # Calls greet method with 1 arg
$person.greet: 'Nushi'; # Calls greet method with 1 arg
:으로 메서드 호출(우선순위 내림)
마지막 줄의 문법은 메서드가 문장의 나머지를 인자 목록으로 취급하게 해요. (다르게 말하면 메서드 호출의 우선순위를 내립니다. 그래서 여기 쓰인 :을 때때로 "우선순위 내림(precedence drop)"이라고 불러요.) 다음 코드가 그 변화의 결과를 보여줘요.
my $band = 'Foo Fighters';
say $band.substr( 0, 3 ).uc; # OUTPUT: «FOO»
say $band.substr: 0, 3 .uc; # OUTPUT: «Foo»
마지막 줄은 바로 위의 줄과 동등하지 않아요. 대신 $band.substr(0, 3.uc)와 같죠. 아마 여기서 의도한 바는 아닐 거예요. 두 번째 메서드 호출에서 uc 메서드가 "3"에 호출되고, 맨 왼쪽 substr의 결과에 호출되지 않기 때문이에요. 다시 말해 substr 메서드가 uc 메서드에 우선순위를 내주는 거예요.
topic에 메서드 호출
invocant 없이(즉 .의 왼쪽에 아무것도 없이) 메서드를 호출하면, 현재 topic 변수를 invocant로 사용해요. 예를 들어
given 'Foo Fighters' {
say .substr: 0, 3; # OUTPUT: «Foo»
}
메서드풍 함수 호출과 함수풍 메서드 호출
서브루틴은 메서드풍 문법으로 호출할 수 있어요. 즉 객체 뒤에 점을 붙여 함수를 호출할 수 있는데, 메서드를 호출하는 것과 비슷해요(선택적으로 :도 사용). 유일한 제한은 함수 이름 앞에 &를 반드시 붙여야 한다는 거예요. 예를 들어
sub greet($name, :$excited = True) {
say "Hello $name" ~ ($excited ?? '!' !! '.')
}
greet "Maria"; # OUTPUT: «Hello Maria!»
"Maria".&greet; # OUTPUT: «Hello Maria!»
"Maria".&greet(); # OUTPUT: «Hello Maria!»
given "Maria" { .&greet } # OUTPUT: «Hello Maria!»
"Maria".&greet(:!excited); # OUTPUT: «Hello Maria.»
"Maria".&greet: :!excited; # OUTPUT: «Hello Maria.»
비슷하게 메서드는 함수풍 문법으로 호출할 수 있어요. 즉 메서드 이름을 먼저 쓰고 그 뒤에 메서드의 인자(Invocant 포함)를 쓰는 방식이에요. 그렇게 하려면 invocant를 첫 번째 인자로 공급하고 :을 뒤에 붙이면 돼요.
class Person {
has Str $.name;
multi method greet { say "Hello, $.name()!" }
multi method greet($name) { say "Hello, $name!" }
};
my $person = Person.new(:name<Ruòxī>, :age(11));
greet $person:; # same as $person.greet;
greet Person: 'Yasmin'; # same as Person.greet('Yasmin');
메서드풍 함수 문법과 함수풍 메서드 문법이 모두 있기 때문에, 어떤 구문 형태가 메서드를 호출하는지 서브루틴을 호출하는지 명확히 하는 것이 특히 중요해요. 특히 Raku는 많은 경우 같은 이름의 메서드와 서브루틴을 함께 제공하므로 이 세부 사항을 놓치기 쉬워요.
예를 들어 다음 단순한 예시는 함수 호출과 메서드 호출 모두 같은 출력을 만들어요.
say 42; # Subroutine call
42.&say; # Still a subroutine call
42.say; # Method call
say 42:; # Also a method call
하지만 서브루틴과 메서드가 모두 존재할 때조차, 올바른 것을 호출하는 것이 큰 차이를 만들 수 있어요. 실제로 보려면 서브루틴과 메서드로서 모두 존재하지만 메서드가 인자를 다른 순서로 기대하는 map을 생각해 보세요.
my @list = 1..9;
sub add1($a) { $a + 1 }
map &add1, @list; # Sub call; expects list last
map @list: &add1; # Method call; expects function last
@list.map(&add1); # Method call; expects function last
&add1.&map(@list); # Sub call; expects list last
연산자(Operators)
자세한 내용은 Operators를 보세요. 연산자는 기호가 많고 합성 가능한 문법을 가진 함수예요. 다른 함수처럼 연산자도 맥락별 사용을 위해 다중 디스패치할 수 있어요.
연산자에는 다섯 가지 유형(배치)이 있고, 각각 한두 개의 인자를 받아요.
++$x # prefix, operator comes before single input
5 + 3 # infix, operator is between two inputs
$x++ # postfix, operator is after single input
<the blue sky> # circumfix, operator surrounds single input
%foo<bar> # postcircumfix, operator comes after first input and surrounds second
메타연산자(Metaoperators)
연산자는 합성될 수 있어요. 흔한 예로 중위(이항) 연산자를 할당과 결합하는 것이 있어요. 어떤 이항 연산자와도 할당을 결합할 수 있어요.
$x += 5 # Adds 5 to $x, same as $x = $x + 5
$x min= 3 # Sets $x to the smaller of $x and 3, same as $x = $x min 3
$x .= child # Equivalent to $x = $x.child
중위 연산자를 [ ]로 감싸면, 단일 입력 목록에 동작해 단일 값을 만들어 내는 새 축약(reduction) 연산자를 만들 수 있어요.
say [+] <1 2 3 4 5>; # OUTPUT: «15»
(((1 + 2) + 3) + 4) + 5 # equivalent expanded version
중위 연산자를 « »(또는 ASCII 동격 << >>)로 감싸면, 두 목록에 쌍별로 동작하는 새 하이퍼 연산자를 만들 수 있어요.
say <1 2 3> «+» <4 5 6> # OUTPUT: «(5 7 9)»
화살표 방향은 목록 크기가 다를 때 무엇을 할지 나타내요.
@a «+« @b # Result is the size of @b, elements from @a will be re-used
@a »+» @b # Result is the size of @a, elements from @b will be re-used
@a «+» @b # Result is the size of the biggest input, the smaller one is re-used
@a »+« @b # Exception if @a and @b are different sizes
단항 연산자를 하이퍼 연산자로 감쌀 수도 있어요.
say -« <1 2 3> # OUTPUT: «(-1 -2 -3)»