signatures — 시그니처와 파라미터 정의

signatures — 시그니처와 파라미터 정의

Signature(시그니처)는 서브루틴·메서드 이름 뒤의 괄호 안, -><-> 화살표 뒤의 블록, my 같은 변수 선언자의 입력, 또는 콜론으로 시작하는 독립 term으로 나타나요.

sub f($x) { }
#    ^^^^ Signature of sub f
my method x() { }
#          ^^ Signature of a method
my $s = sub (*@a) { }
#           ^^^^^ Signature of an anonymous function

for <a b c> -> $x { }
#              ^^   Signature of a Block

my ($a, @b) = 5, (6, 7, 8);
#  ^^^^^^^^ Signature of a variable declarator

my $sig = :($a, $b);
#          ^^^^^^^^ Standalone Signature object

시그니처 리터럴은 콜백이나 클로저의 시그니처를 정의하는 데 쓸 수 있어요.

sub f(&c:(Int)) { }
sub will-work(Int) { }
sub won't-work(Str) { }
f(&will-work);

f(&won't-work);
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: «X::TypeCheck::Binding::Parameter: Constraint type check failed in binding to parameter '&c'␤»

f(-> Int { 'this works too' } );

숫자 리터럴을 포함한 어떤 종류의 리터럴도 시그니처의 일부로 쓸 수 있어요. 보통 multi와 함께 쓰이죠.

proto stuff(|) {*}
multi stuff(33) { 58 }
multi stuff(⅓) { 43 }
multi stuff(Int)  { 3 }
multi stuff(Complex)  { 66 }
say stuff($_) for (33, ⅓, i, 48); # OUTPUT: «58␤43␤66␤3␤»

다만 시그니처 리터럴로 TrueFalse는 쓸 수 없어요. 항상 성공(또는 실패)하니까요. 그렇게 쓰면 경고가 나요:

sub foo(True) {};
my $sig =  :( True );

둘 다 "Literal values in signatures are smartmatched against and smartmatch with True will always succeed. Use the where clause instead."라고 경고해요. False를 쓰면 비슷한 경고가 나요.

시그니처를 List에 대해 smartmatch하는 것은 지원돼요.

my $sig = :(Int $i, Str $s);
say (10, 'answer') ~~ $sig;
# OUTPUT: «True␤»
my $sub = sub ( Str $s, Int $i ) { return $s xx $i };
say $sub.signature ~~ :( Str, Int );
# OUTPUT: «True␤»
given $sig {
    when :(Str, Int) { say 'mismatch' }
    when :($, $)     { say 'match' }
    default          { say 'no match' }
}
# OUTPUT: «match␤»

:($, $)가 스칼라 익명 인자 두 개짜리 Signature를 나타내고, 그것이 $sig의 더 일반적인 버전이기 때문에 두 번째 when 절이 매칭돼요.

Hash에 대해 smartmatch할 때는 시그니처가 Hash의 키로 구성된다고 가정해요.

my %h = left => 1, right => 2;
say %h ~~ :(:$left, :$right);
# OUTPUT: «True␤»

Signature 리터럴은 문자열/숫자 리터럴을 담을 수 있어요.

my $sig = :('Þor', Str, Int);
say <Þor Hammer 1> ~~ $sig; # OUTPUT: «True␤»

시그니처는 named slurpy 파라미터를 담을 수 있는데, 추가 named 인자를 한 큰 해시로 슬러프(slurp)해요. 어떤 이름이든 가능하지만 관례로 밑줄을 써요:

my $sig = :($p, :$n, *%_);

시그니처는 slurpy positional 파라미터도 담을 수 있는데, 반드시 모든 positional 파라미터 중 마지막이어야 해요. 추가 positional 인자를 한 큰 Array로 슬러프해요. slurpy positional 파라미터는 세 종류예요:

my $sig = :($p, :$n, **@_); # keep arguments as they are: @_ contains one entry per argument
my $sig = :($p, :$n,  *@_); # flatten any lists
my $sig = :($p, :$n,  +@_); # set @_ = argument if it's a single list, else act like **@_

출처: Signatures - Raku Documentation

파라미터 구분자

시그니처는 콤마로 구분된 0개 이상의 Parameter로 구성돼요.

my $sig = :($a, @b, %c);
sub add($a, $b) { $a + $b };

예외로, 첫 번째 파라미터 뒤에 콤마 대신 콜론이 올 수 있는데, 이는 메서드의 **호출자(invocant)**를 표시해요. 이것은 그렇지 않았다면 일반 positional 파라미터가 됐을 것과 구분하기 위함이에요. 호출자는 메서드를 호출하는 데 쓰인 객체로, 보통 self에 바인딩돼요. 시그니처에 지정하면 바인딩되는 변수 이름을 바꿀 수 있어요.

method ($a: @b, %c) {};       # first argument is the invocant

class Foo {
    method whoami($me:) {
        "Well I'm class $me.^name(), of course!"
    }
}
say Foo.whoami; # OUTPUT: «Well I'm class Foo, of course!␤»
say Foo.^methods.first(*.name eq 'whoami').signature ~~ :($: *%) ; # OUTPUT: «True␤»

또 다른 예외는 이중 세미콜론 ;;인데, multi 시그니처에서 콤마 하나를 대신할 수 있어요. 뒤따르는 파라미터들이 다중 디스패치의 우선순위에 기여하지 않아야 한다는 뜻이에요.

타입 제약

파라미터는 선택적으로 타입 제약(기본은 Any)을 가질 수 있어요. 이것으로 함수에 허용되는 입력을 제한할 수 있어요.

my $sig = :(Int $a, Str $b);

타입 제약은 컴파일 타임에 정의된 어떤 값이든 될 수 있어요.

subset Positive-integer of Int where * > 0;
sub divisors(Positive-integer $n) { $_ if $n %% $_ for 1..$n };
CATCH { default { put .^name, ': ', .Str; .resume } };

divisors 2.5;
# OUTPUT: «X::TypeCheck::Binding::Parameter: Type check failed in binding to parameter '$n'; expected Positive-integer but got Rat (2.5)␤»

divisors -3;
# OUTPUT: «X::TypeCheck::Binding::Parameter: Constraint type check failed in binding to parameter '$n'; expected Positive-integer but got Int (-3)␤»

위 코드에서 타입 제약이 두 단계에서 강제되는 점을 주의하세요. 첫 단계는 subset이 기반으로 하는 타입(여기서 Int)에 속하는지 검사해요. 실패하면 Type check 오류가 나요. 그 필터를 통과하면 subset을 정의한 제약이 검사돼, 실패하면 Constraint type check 오류가 나요.

타입 제약은 여러 허용 타입을 정의할 수 있어요.

sub abbrev($arg where Str|List|Hash) {...} # throws if $arg is not one of those types

익명 인자도 괜찮아요. 파라미터를 이름으로 참조할 필요가 없으면, 예를 들어 multi에서 다른 시그니처를 구분하거나 Callable의 시그니처를 검사할 때요.

my $sig = :($, @, %a);          # two anonymous and a "normal" parameter
$sig = :(Int, Positional);      # just a type is also fine (two parameters)
sub baz(Str) { "Got passed a Str" }

타입 제약은 타입 캡처(type capture)일 수도 있어요 (뒤에서 설명).

이런 명목 타입에 더해, 파라미터에 타입 검사를 통과하려면 true 값을 돌려줘야 하는 코드 블록 형태의 추가 제약을 둘 수 있어요.

sub f(Real $x where { $x > 0 }, Real $y where { $y >= $x }) { }

where 절의 코드에는 몇 가지 제약이 있어요. 부수 효과를 만드는 것(예: 출력, 이터레이터에서 끌어오기, state 변수 증가)은 지원되지 않고, 쓰면 놀라운 결과가 나올 수 있어요. 또 where 절 코드는 일부 구현에서 단일 타입 검사에 두 번 이상 실행될 수 있어요.

where 절은 코드 블록일 필요가 없어요. where 절 오른쪽의 무엇이든 그 인자를 smartmatch하는 데 쓰여요. 그래서 이렇게도 쓸 수 있어요:

multi factorial(Int $ where 0) { 1 }
multi factorial(Int $x)        { $x * factorial($x - 1) }

첫 번째는 이렇게 줄일 수 있어요:

multi factorial(0) { 1 }

즉 익명 파라미터에 리터럴을 타입·값 제약으로 직접 쓸 수 있어요.

팁: 예를 들어 여러 조건이 있을 때 실수로 블록을 빠뜨리지 않도록 주의하세요.

-> $y where   .so && .name    {}( sub one   {} ); # WRONG!!
-> $y where { .so && .name }  {}( sub two   {} ); # OK!
-> $y where   .so &  .name.so {}( sub three {} ); # Also good

첫 버전은 틀렸고, sub 객체가 문자열로 강제 변환된다는 경고가 나요. 이유는 그 표현식이 ($y ~~ ($y.so && $y.name))와 동일하기 때문이에요. 즉 ".so를 호출하고, True이면 .name을 호출하고, 그것도 True이면 그 값을 smartmatch에 쓴다…"는 뜻이에요. smartmatch되는 것은 (.so && .name)결과인데, 우리가 원하는 건 .so.name이 둘 다 truthy인지 검사하는 거예요. 그래서 명시적 Block이나 Junction이 올바른 버전이에요.

Signature 안에 sub-signature의 일부가 아닌 이전 인자들은, 인자 뒤에 오는 where 절에서 접근할 수 있어요. 따라서 마지막 인자의 where 절은 시그니처에서 sub-signature의 일부가 아닌 모든 인자에 접근할 수 있어요. sub-signature에는 where 절을 sub-signature 안에 두세요.

sub foo($a, $b where * == $a ** 2) { say "$b is a square of $a" }
foo 2, 4; # OUTPUT: «4 is a square of 2␤»»
# foo 2, 3;
# OUTPUT: «Constraint type check failed in binding to parameter '$b'…»

선택적 인자 제약

선택적 인자에도 제약을 둘 수 있어요. 어떤 파라미터의 where 절이든, 선택적이고 호출자가 주지 않았어도 실행돼요. 그 경우 where 절 안에서 정의되지 않은 값에 대비해야 할 수 있어요.

sub f(Int $a, UInt $i? where { !$i.defined or $i > 5 }) { ... }

Slurpy 인자 제약

slurpy 인자는 타입 제약을 가질 수 없어요. Junction과 결합한 where 절로 그 효과를 낼 수 있어요.

sub f(*@a where {$_.all ~~ Int}) { say @a };
f(42);
f(<a>);
CATCH { default { say .^name, ' ==> ', .Str }  }
# OUTPUT: «[42]␤Constraint type check failed in binding to parameter '@a' ...»

Named 인자 제약

named 인자에 대한 제약은 colon-pair의 값 부분에 적용돼요.

sub f(Int :$i){};
f :i<forty-two>;
CATCH { default { say .^name, ' ==> ', .Str }  }
# OUTPUT: «X::TypeCheck::Binding::Parameter ==> Type check failed in
# binding to parameter '$i'; expected Int but got Str ("forty-two")␤»

인자 정의성(definiteness) 제약

보통 타입 제약은 파라미터의 값이 올바른 타입인지만 검사해요. 중요한 점은 객체 인스턴스타입 객체 모두 그런 제약을 만족시킨다는 거예요. 아래에서 볼 수 있죠:

say  42.^name;    # OUTPUT: «Int␤»
say  42 ~~ Int;   # OUTPUT: «True␤»
say Int ~~ Int;   # OUTPUT: «True␤»

42Int 둘 다 매칭을 만족시키는 걸 볼 수 있어요.

때로는 객체 인스턴스(42)와 타입 객체(Int)를 구분해야 할 때가 있어요. 이 코드를 봐요:

sub limit-lines(Str $s, Int $limit) {
    my @lines = $s.lines;
    @lines[0 .. min @lines.elems, $limit].join("\n")
}
say (limit-lines "a \n b \n c \n d \n", 3).raku; # "a \n b \n c \n d "
say limit-lines Str, 3;
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: «X::Multi::NoMatch: Cannot resolve caller lines(Str: );
# none of these signatures match:
#     (Str:D $: :$count!, *%_)
#     (Str:D $: $limit, *%_)
#     (Str:D $: *%_)»
say limit-lines "a \n b", Int; # Always returns the max number of lines

여기서 우리는 타입 객체가 아니라 문자열 인스턴스만 다루고 싶어요. 그러려면 :D 타입 제약을 쓸 수 있어요. 이 제약은 전달된 값이 객체 인스턴스인지 검사하는데, DEFINITE (메타)메서드를 호출하는 것과 비슷해요.

먼저 단순한 Int 예제의 오른쪽에 :D를 적용해 볼게요:

say  42 ~~ Int:D;  # OUTPUT: «True␤»
say Int ~~ Int:D;  # OUTPUT: «False␤»

위에서 Int:D에 매칭되는 건 42뿐인 걸 볼 수 있어요.

limit-lines로 돌아가서, 시그니처를 고쳐서 오류를 일찍 잡을 수 있어요:

sub limit-lines(Str:D $s, Int $limit) { };
say limit-lines Str, 3;
CATCH { default { put .^name ~ '--' ~ .Str } };
# OUTPUT: «Parameter '$s' of routine 'limit-lines' must be an object instance of type 'Str',
#          not a type object of type 'Str'.  Did you forget a '.new'?»

이게 이전의 실패 방식보다 훨씬 나아요. 실패 이유가 더 명확하니까요.

타입 객체가 루틴이 받아들이는 유일하게 타당한 것일 수도 있어요. 이것은 :U 타입 제약으로 하는데, 전달된 값이 객체 인스턴스가 아니라 타입 객체인지 검사해요. 다시 Int 예제를 :U로 적용해 볼게요:

say  42 ~~ Int:U;  # OUTPUT: «False␤»
say Int ~~ Int:U;  # OUTPUT: «True␤»

이제 42Int:U에 매칭에 실패하고 Int는 성공해요.

더 실용적인 예제가 있어요:

sub can-turn-into(Str $string, Any:U $type) {
   return so $string.$type;
}
say can-turn-into("3", Int);        # OUTPUT: «True␤»
say can-turn-into("6.5", Int);      # OUTPUT: «True␤»
say can-turn-into("6.5", Num);      # OUTPUT: «True␤»
say can-turn-into("a string", Num); # OUTPUT: «False␤»

can-turn-into를 두 번째 파라미터로 객체 인스턴스를 주고 호출하면 의도대로 제약 위반이 나요:

say can-turn-into("a string", 123);
# OUTPUT: «Parameter '$type' of routine 'can-turn-into' must be a type object
# of type 'Any', not an object instance of type 'Int'...»

일반 동작(인자가 인스턴스인지 타입 객체인지 제약하지 않는 것)을 명시적으로 나타내려면 :_를 쓸 수 있지만, (이 종류의) 인자에 대한 기본 제약이므로 꼭 쓸 필요는 없어요. 따라서 :(Num:_ $):(Num $)와 같아요.

요약하면, 타입 스마일리(type smileys)라고도 부르는 이 타입 제약들의 빠른 예시가 있어요:

# Checking a type object
say Int ~~ Any:D;    # OUTPUT: «False␤»
say Int ~~ Any:U;    # OUTPUT: «True␤»
say Int ~~ Any:_;    # OUTPUT: «True␤»

# Checking a subset
subset Even of Int where * %% 2;
say 3 ~~ Even:D;     # OUTPUT: «False␤»
say 3 ~~ Even:U;     # OUTPUT: «False␤»
say Int ~~ Even:U;   # OUTPUT: «Use of uninitialized value of type Int in numeric context␤...␤True␤»

# Checking an object instance
say 42 ~~ Any:D;     # OUTPUT: «True␤»
say 42 ~~ Any:U;     # OUTPUT: «False␤»
say 42 ~~ Any:_;     # OUTPUT: «True␤»

# Checking a user-supplied class
class Foo {};
say Foo ~~ Any:D;    # OUTPUT: «False␤»
say Foo ~~ Any:U;    # OUTPUT: «True␤»
say Foo ~~ Any:_;    # OUTPUT: «True␤»

# Checking an instance of a class
my $f = Foo.new;
say $f  ~~ Any:D;    # OUTPUT: «True␤»
say $f  ~~ Any:U;    # OUTPUT: «False␤»
say $f  ~~ Any:_;    # OUTPUT: «True␤»

Classes and Objects 문서가 인스턴스·타입 객체 개념과 .DEFINITE 메서드로 그것들을 알아내는 방법을 더 자세히 다뤄요.

모든 파라미터는 값을 갖는다는 점을 기억하세요. 선택적 파라미터조차 명시적 타입 제약이 있으면 그 제약 타입의 타입 객체를 기본값으로 가져요. 명시적 타입 제약이 없으면 메서드, submethod, 서브루틴은 기본값이 Any 타입 객체이고 블록은 Mu 타입 객체예요. 즉 :D 타입 스마일리를 쓰면 기본값을 주거나 파라미터를 필수로 만들어야 해요. 그렇지 않으면 기본값이 타입 객체가 되어 정의성 제약에 실패해요.

sub divide (Int:D :$a = 2, Int:D :$b!) { say $a/$b }
divide :1a, :2b; # OUTPUT: «0.5␤»

기본값은 그 파라미터(위치든 named든)가 전혀 값을 받지 못할 때 작동해요.

sub f($a = 42){
  my $b is default('answer');
  say $a;
  $b = $a;
  say $b
};
f;     # OUTPUT: «42␤42␤»
f Nil; # OUTPUT: «Nil␤answer␤»

$a는 42를 기본값으로 가져요. 값이 없으면 Signature에 선언된 기본값이 $a에 할당돼요. 그런데 두 번째 경우엔 파라미터 $a가 값을 받는데, 그게 마침 Nil이에요. 어떤 변수Nil을 할당하면 기본값으로 재설정되는데, $b의 기본값은 default 트레잇으로 'answer'로 선언됐어요. 그것이 f를 두 번째로 호출했을 때 일어나는 일이에요. 루틴 파라미터와 변수는 기본값을 다르게 다뤄요. 각 경우에 기본값을 선언하는 방식이 다르다는 것(파라미터는 =를, 변수는 default 트레잇을)에서 부분적으로 분명해져요.

주의: 6.c 언어에서 :U/:D 제약 변수의 기본값은 그런 제약을 가진 타입 객체였는데, 초기화할 수 없어서 예를 들어 .= 연산자를 쓸 수 없었어요.

use v6.c;
my Int:D $x .= new: 42;
# OUTPUT: You cannot create an instance of this type (Int:D)
# in block <unit> at -e line 1

6.d 언어에서 기본 기본값은 스마일리 제약이 없는 타입 객체예요:

use v6.d;
my Int:D $x .= new: 42; # OUTPUT: «42␤»

용어에 관한 마지막 언급: 이 섹션은 인자의 정의성을 제약하는 타입 스마일리 :D, :U 사용에 관한 거예요. 가끔 definednessdefiniteness(정의성)의 동의어로 쓰이기도 하는데, 용어들이 미묘하게 다른 의미를 가지므로 혼동될 수 있어요.

위에서 설명했듯 definiteness(정의성)는 타입 객체와 객체 인스턴스의 구분에 관한 것이에요. 타입 객체는 항상 indefinite(부정)이고, 객체 인스턴스는 항상 definite(정)이에요. 객체가 타입 객체/indefinite인지 객체 인스턴스/definite인지는 DEFINITE (메타)메서드로 확인할 수 있어요.

Definitenessdefinedness와 구분해야 하는데, 후자는 정의된 객체와 정의되지 않은 객체의 차이에 관한 거예요. 객체가 정의됐는지는 Mu 클래스에 구현된 defined 메서드로 확인할 수 있어요. 기본적으로 타입 객체는 undefined로 간주되고 객체 인스턴스는 defined로 간주돼요. 즉 .defined는 타입 객체에서 False를, 그 외에선 True를 돌려줘요. 하지만 이 기본 동작은 하위 클래스가 오버라이드할 수 있어요. 기본 .defined 동작을 오버라이드하는 하위 클래스의 예가 Failure인데, 인스턴스화된 Failure조차 undefined 값처럼 동작해요:

my $a = Failure;                # Initialize with type object
my $b = Failure.new("foo");     # Initialize with object instance
say $a.DEFINITE;                # OUTPUT: «False␤» : indefinite type object
say $b.DEFINITE;                # OUTPUT: «True␤»  : definite object instance
say $a.defined;                 # OUTPUT: «False␤» : default response
say $b.defined;                 # OUTPUT: «False␤» : .defined override

Callable의 시그니처 제약

Callable 파라미터의 시그니처는 파라미터 바로 뒤에 Signature 리터럴을 지정해 제약할 수 있어요 (공백 없이):

sub apply(&l:(Int:D --> Int:D), Int:D \n) {
    l(n)
}

sub double(Int:D \x --> Int:D) { 2 * x }
say apply &double, 10;         # OUTPUT: «20␤»

sub general-double(Numeric:D \x --> Numeric:D) { 2 * x }
say apply &general-double, 10; # OUTPUT: «Signature constraint check failed(…)␤»

&l의 시그니처를 제약하는 축약 문법은 & 시길이 있기 때문만 가능해요. $-시길 callable 파라미터에서는 where가 있는 긴 버전을 써야 해요:

sub apply($l where .signature ~~ :(Int:D --> Int:D), Int:D \n) {
    $l(n)
}

&l이나 $l 같은 제약 callable 파라미터에 타입이 있는 람다를 넘길 수도 있어요:

say apply -> Int:D \x --> Int:D { 2 * x }, 3;  # OUTPUT: «6␤»
say apply -> Int:D \x --> Int:D { x ** 3 }, 3; # OUTPUT: «27␤»

타입 스마일리가 없는 제약도 가능해요.

sub play-with-tens(&c:(Int, Str)) { say c(10, 'ten') }
play-with-tens ->   Int \i, Str \s { s ~ i }                 # OUTPUT: «ten10»
play-with-tens ->   Int \i, Str \s { s x (1..30).roll mod i} # OUTPUT: «tenten(…)ten␤»

반환 타입 제약

Routine의 반환 타입을 제약하는 여러 방법이 있어요. 아래 모든 버전이 현재 유효하고, 루틴의 성공적인 실행 시 타입 검사를 강제해요.

NilFailure는 어떤 타입 제약과 무관하게 항상 반환 타입으로 허용돼요. 이렇게 Failure를 돌려주고 호출 체인 아래로 전달할 수 있어요.

sub foo(--> Int) { Nil };
say foo.raku; # OUTPUT: «Nil␤»

캡처된 타입도 반환 타입 제약으로 쓸 수 있어요 (자세한 내용은 거기 참조).

반환 타입 화살표: -->

시그니처에서 반환 타입(또는 상수)을 나타내는 이 형태가 선호돼요. 다른 쪽은 상수 값을 다룰 수 없는데 이건 다루거든요. 일관성을 위해 이 사이트에서 인정하는 유일한 형태예요.

반환 타입 화살표는 파라미터 목록 끝에 두는데, 앞에 ,가 있든 없든 괜찮아요.

sub greeting1(Str $name  --> Str) { say "Hello, $name" } # Valid
sub greeting2(Str $name, --> Str) { say "Hello, $name" } # Valid

sub favorite-number1(--> 42) {        } # OUTPUT: 42
sub favorite-number2(--> 42) { return } # OUTPUT: 42

타입 제약이 상수 표현식이면 루틴의 반환값으로 쓰여요. 그 루틴의 어떤 return 문장도 인자 없이 있어야 해요.

sub foo(Str $word --> 123) { say $word; return; }
my $value = foo("hello"); # OUTPUT: hello
say $value;               # OUTPUT: 123
# The code below will not compile
sub foo(Str $word --> 123) { say $word; return $word; }
my $value = foo("hello");
say $value;

returns

시그니처 선언 뒤에 오는 키워드 returns-->와 같은 기능을 하는데, 이 형태가 상수 값에서는 동작하지 않는다는 단점이 있어요. 블록에서도 쓸 수 없어요. 그래서 pointy arrow 형태가 항상 선호돼요.

sub greeting(Str $name) returns Str { say "Hello, $name" }; # Valid
say &greeting.signature  # OUTPUT: «(Str $name --> Str)␤»
sub favorite-number returns 42 { }; # This will fail.

of

ofreturns 키워드의 실제 이름일 뿐이에요.

sub foo() of Int { 42 }; # Valid
say &foo.signature       # OUTPUT: «( --> Int)␤»
sub foo() of 42 { };     # This will fail.

prefix (C-스타일) 형태

my Type $var = 20;처럼 변수에 타입 제약을 두는 것과 비슷한데, $var가 루틴에 대한 정의라는 점만 달라요.

my Int sub bar { 1 };     # Valid
say &bar.signature        # OUTPUT: «( --> Int)␤»
my 42 sub bad-answer { }; # This will fail.

강제 변환 타입 (Coercion type)

한 타입을 받되 자동으로 다른 타입으로 강제 변환하려면, 대상 타입의 인자로 받는 타입을 써요. 받는 타입이 Any면 생략할 수 있어요.

sub f(Int(Str) $becomes-int, Str() $becomes-str) {
    say $becomes-int.^name ~ ' ' ~ $becomes-str.^name
}
f '10', 10;
# OUTPUT: «Int Str␤»

sub foo(Date(Str) $d) { say $d.^name; say $d };
foo "2016-12-01";
# OUTPUT: «Date␤2016-12-01␤»

강제 변환은 변환할 타입의 이름을 가진 메서드를 호출해서 수행돼요. 이 예제에서는 Str 클래스의 내장 메서드 Date를 호출하고 있어요. 메서드가 올바른 타입을 돌려줄 것으로 가정해요 — 결과에 대한 추가 검사는 현재 수행되지 않아요.

반환 타입에도 강제 변환을 적용할 수 있어요:

sub square-str (Int $x --> Str(Int)) {
    $x²
}

for 2,4, *²  … 256 -> $a {
    say $a, "² is ", square-str( $a ).chars, " figures long";
}

# OUTPUT: «2² is 1 figures long␤
#          4² is 2 figures long␤
#          16² is 3 figures long␤
#          256² is 5 figures long␤»

이 예제에서 반환 타입을 Str로 강제 변환해서 문자열 메서드(문자 수 같은)를 직접 적용할 수 있어요.

주의: 적절한 메서드가 인자에서 사용 가능해야 해요. 커스텀 타입을 강제 변환하려 할 때 조심하세요.

class Foo { }

sub bar(Foo(Int) $x) { say $x }

bar(3);
# OUTPUT: «Impossible coercion from 'Int' into 'Foo': no acceptable coercion method found␤»

타입 캡처 (Type captures)

타입 캡처는 함수가 호출되는 시점까지 타입 제약의 지정을 미룰 수 있게 해 줘요. 시그니처와 함수 본문 양쪽에서 타입을 참조할 수 있게 해 주죠.

sub f(::T $p1, T $p2, ::C){
    # $p1 and $p2 are of the same type T, that we don't know yet
    # C will hold a type we derive from a type object or value
    my C $division = $p1 / $p2;
    return sub (T $p1) {
        $division * $p1;
    }
}

# The first parameter is Int and so must be the 2nd.
# We derive the 3rd type from calling the operator that is used in &f.
my &s = f(10, 2, Int.new / Int.new);
say s(2); # 10 / 2 * 2 == 10

캡처된 타입은 반환 타입 제약으로 쓸 수 있어요 (반환 타입 제약 참조):

sub cast-by-example(Any $x, ::T $example --> T) { T($x) }
sub cast-or-create(Any $x, ::T $example --> T:D) { with $x { T($x) } else { T.new } }

캡처된 타입은 다른 파라미터를 그 타입으로 강제 변환하는 데도 쓸 수 있어요 (고정 타입에 대한 'Coercion types' 섹션처럼요). 예를 들어 이 함수는 두 번째 파라미터 b(Cool 타입이어야 함)를 첫 파라미터 a가 가진 타입으로 강제 변환해요:

sub accum( ::T \a, T(Cool) \b ) { a += b };

my $t = 3;        # Int
accum( $t, 5/3 ); # OUTPUT: «4␤»

my $t = 3.0;      # Rat
accum( $t, 5/3 ); # OUTPUT: «4.666667␤»

my $t = 3.0;      # Rat, and the second parameter an Array
accum ( $t, ["x","y"] ); # OUTPUT: «5.0»

타입 캡처는 타입 제약의 대상이 될 수도 있어요. 위 예제에서, 이런 무의미한 호출을 막으려면 시그니처를 다음 중 하나로 바꾸면 돼요:

    (Numeric ::T \a, T(Cool) \b )
    (::T Numeric \a, T(Cool) \b )

Parameter 타입 참조의 method type_captures 섹션도 참고하세요.

Positional vs. named 인자

인자는 positional이거나 named일 수 있어요. 기본적으로 인자는 positional인데, slurpy 해시와 앞에 콜론 :이 붙은 인자는 예외예요. 후자를 colon-pair라고 불러요. 다음 시그니처들이 무엇을 나타내는지 봐요:

$sig1 = :($a);               # a positional argument
$sig2 = :(:$a);              # a named argument of name 'a'
$sig3 = :(*@a);              # a slurpy positional argument
$sig4 = :(*%h);              # a slurpy named argument

호출자 쪽에서 positional 인자는 선언된 순서대로 전달돼요.

sub pos($x, $y) { "x=$x y=$y" }
pos(4, 5);                          # OUTPUT: «x=4 y=5»

named 인자·파라미터의 경우, 인자를 파라미터에 매핑할 때 이름만 사용돼요. fat arrow로 Pair를 만들 때는 유효한 식별자를 키로 가진 것만 named 인자로 인식돼요.

sub named(:$x, :$y) { "x=$x y=$y" }
named( y => 5, x => 4);             # OUTPUT: «x=4 y=5»

named 인자와 같은 이름을 가진 변수를 써서 루틴을 호출할 수 있어요. 그 경우 :가 호출에 쓰여서 변수의 이름이 인자의 키로 이해돼요.

sub named-shortcut( :$shortcut ) {
    say "Looks like $shortcut"
}
named-shortcut( shortcut => "to here"); # OUTPUT: «Looks like to here␤»
my $shortcut = "Þor is mighty";
named-shortcut( :$shortcut );           # OUTPUT: «Looks like Þor is mighty␤»

named 인자가 변수 이름과 다른 이름을 갖는 것도 가능해요:

sub named(:official($private)) { "Official business!" if $private }
named :official;

Slurpy 파라미터

함수가 가변 개수의 인자를 받을 수 있으면 variadic이라고 해요. 즉 arity(인자 수)가 고정돼 있지 않은 거죠. 따라서 선택적·named·slurpy 파라미터는 그것을 쓰는 루틴을 variadic하게 만들고, 확장해서 variadic 인자라고 불러요. 여기서는 slurpy 파라미터, 줄여서 slurpies에 집중할게요.

배열·해시 파라미터는 앞에 단일(*) 또는 이중 별표(**), 또는 앞 플러스(+)로 slurpy임을 표시할 수 있어요. slurpy 파라미터는 임의의 개수(0개 이상) 인자에 바인딩할 수 있고, 시길과 호환되는 타입이 돼요.

어떤 사람이 국수를 slurp하듯 함수에 남은 인자를 slurp하기 때문에 "slurpy"라고 불러요.

my $sig1 = :($a, @b);  # exactly two arguments, second must be Positional
my $sig2 = :($a, *@b); # at least one argument, @b slurps up any beyond that
my $sig3 = :(*%h);     # no positional arguments, but any number
                       # of named arguments

sub one-arg (@)  { }
sub slurpy  (*@) { }
one-arg (5, 6, 7); # ok, same as one-arg((5, 6, 7))
slurpy  (5, 6, 7); # ok
slurpy   5, 6, 7 ; # ok
# one-arg(5, 6, 7) ; # X::TypeCheck::Argument
# one-arg  5, 6, 7 ; # X::TypeCheck::Argument

sub named-names (*%named-args) { %named-args.keys };
say named-names :foo(42) :bar<baz>; # OUTPUT: «foo bar␤»

Positional과 named slurpy를 결합할 수 있어요. named 인자(즉 Pair들)는 지정된 해시에, positional 인자는 배열에 모여요:

sub combined-slurpy (*@a, *%h) { { array => @a, hash => %h } }
# or: sub combined-slurpy (*%h, *@a) { ... }

say combined-slurpy(one => 1, two => 2);
# OUTPUT: «{array => [], hash => {one => 1, two => 2}}␤»
say combined-slurpy(one => 1, two => 2, 3, 4);
# OUTPUT: «{array => [3 4], hash => {one => 1, two => 2}}␤»
say combined-slurpy(one => 1, two => 2, 3, 4, five => 5);
# OUTPUT: «{array => [3 4], hash => {five => 5, one => 1, two => 2}}␤»
say combined-slurpy(one => 1, two => 2, 3, 4, five => 5, 6);
# OUTPUT: «{array => [3 4 6], hash => {five => 5, one => 1, two => 2}}␤»

slurpy(사실 어떤 종류의 variadic 파라미터든) 뒤에는 positional 파라미터가 허용되지 않는다는 점을 기억하세요:

:(*@args, $last);
# ===SORRY!=== Error while compiling:
# Cannot put required parameter $last after variadic parameters

보통 slurpy 파라미터는 Array(또는 호환 타입)를 만들고, 각 인자마다 새 Scalar 컨테이너를 만들어 각 인자의 값을 그 Scalar에 할당해요. 원래 인자에 중간 Scalar가 있었다면 이 과정에서 우회되고, 호출된 함수 안에서는 사용할 수 없어요.

시길이 있는 파라미터는 항상 수집된 인자에 컨텍스트를 부과해요. 시길이 없는 파라미터도 + 기호를 앞에 붙여 slurpily 쓸 수 있는데, 처음 시작한 타입 그대로 작업해요:

sub zipi( +zape ) {
    zape.^name => zape
};
say zipi( "Hey " ); # OUTPUT: «List => (Hey )␤»
say zipi( 1...* );  # OUTPUT: «Seq => (...)␤»

slurpy 파라미터는 일부 트레잇·수정자와 결합하면 특별한 동작을 하는데, slurpy 배열 파라미터 섹션에서 설명해요.

메서드는 선언된 slurpy named 파라미터가 없으면 자동으로 *%_ slurpy named 파라미터를 추가해요.

Slurpy 배열 파라미터의 종류

slurpy 배열 파라미터에는 세 가지 변형이 있어요.

  • 단일 별표 형태 *@는 전달된 인자를 평탄화해요.
  • 이중 별표 형태 **@는 인자를 평탄화하지 않아요.
  • 플러스 형태 +@는 단일 인자 규칙(single argument rule)에 따라 평탄화해요.

각각을 자세히 설명할게요.

평탄화 slurpy: *@

별표 하나로 선언된 slurpy 파라미터는 맨 Iterable의 한 겹 이상을 녹여서 인자를 평탄화해요.

my @array = <a b c>;
my $list := <d e f>;
sub a(*@a)  { @a.raku.say };
a(@array);                 # OUTPUT: «["a", "b", "c"]␤»
a(1, $list, [2, 3]);       # OUTPUT: «[1, "d", "e", "f", 2, 3]␤»
a([1, 2]);                 # OUTPUT: «[1, 2]␤»
a(1, [1, 2], ([3, 4], 5)); # OUTPUT: «[1, 1, 2, 3, 4, 5]␤»
a(($_ for 1, 2, 3));       # OUTPUT: «[1, 2, 3]␤»

단일 별표 slurpy는 주어진 모든 iterable을 평탄화해서, 콤마로 만든 객체를 효과적으로 최상위 수준으로 끌어올려요.

개별 인자에 대해 이런 평탄화를 막으려면 항목 컨텍스트에 넣으면 돼요. $ 시길을 앞에 붙이거나 .item 메서드를 적용하면 돼요:

sub normally-this-flattens(*@pos) { say @pos; }
my @array = [5,6];
normally-this-flattens([1,2], $[3,4], $@array);        # OUTPUT: «[1 2 [3 4] [5 6]]␤»
normally-this-flattens([1,2], [3,4].item, @array.item) # OUTPUT: «[1 2 [3 4] [5 6]]␤»

비평탄화 slurpy: **@

별표 두 개로 선언된 slurpy 파라미터는 리스트 안의 어떤 Iterable 인자도 평탄화하지 않고, 인자를 거의 그대로 유지해요:

my @array = <a b c>;
my $list := <d e f>;
sub b(**@b) { @b.raku.say };
b(@array);                 # OUTPUT: «[["a", "b", "c"],]␤»
b(1, $list, [2, 3]);       # OUTPUT: «[1, ("d", "e", "f"), [2, 3]]␤»
b([1, 2]);                 # OUTPUT: «[[1, 2],]␤»
b(1, [1, 2], ([3, 4], 5)); # OUTPUT: «[1, [1, 2], ([3, 4], 5)]␤»
b(($_ for 1, 2, 3));       # OUTPUT: «[(1, 2, 3),]␤»

이중 별표 slurpy는 중첩 콤마 객체를 숨기지 않고 slurpy 배열에 그대로 남겨 둬요.

단일 인자 규칙 slurpy: +@

플러스로 만든 slurpy 파라미터는 단일 인자 규칙을 발동시키는데, 컨텍스트에 따라 slurpy 인자를 어떻게 다룰지 결정해요. 기본적으로 단일 인자만 전달되고 그 인자가 Iterable이면, 그 인자가 slurpy 파라미터 배열을 채우는 데 쓰여요. 그 외의 경우에 +@**@처럼 동작해요.

my @array = <a b c>;
my $list := <d e f>;
sub c(+@b) { @b.raku.say };
c(@array);                 # OUTPUT: «["a", "b", "c"]␤»
c(1, $list, [2, 3]);       # OUTPUT: «[1, ("d", "e", "f"), [2, 3]]␤»
c([1, 2]);                 # OUTPUT: «[1, 2]␤»
c(1, [1, 2], ([3, 4], 5)); # OUTPUT: «[1, [1, 2], ([3, 4], 5)]␤»
c(($_ for 1, 2, 3));       # OUTPUT: «[1, 2, 3]␤»

추가 논의·예시는 "Slurpy Conventions for Functions"를 참고하세요.

인자 별칭

colon-pair 문법으로 인자에 별칭을 제공할 수 있어요:

sub alias-named(:color(:$colour), :type(:class($kind))) {
    say $colour ~ " " ~ $kind
}
alias-named(color => "red", type => "A");    # both names can be used
alias-named(colour => "green", type => "B"); # more than two names are ok
alias-named(color => "white", class => "C"); # every alias is independent

콜론 :의 존재가 새 named 인자를 만드는지 아닌지를 결정해요. :$colour는 별칭 변수의 이름일 뿐 아니라 새 named 인자(두 번째 호출에서 쓰임)도 돼요. 반면 $kind는 별칭 변수의 이름일 뿐이고 새 named 인자를 만들지 않아요. 별칭의 더 많은 용법은 sub MAIN에서 찾을 수 있어요.

named 인자를 가진 함수는 Pair|로 역참조해 named 인자로 바꿔 동적으로 호출할 수 있어요.

multi f(:$named) { note &?ROUTINE.signature };
multi f(:$also-named) { note &?ROUTINE.signature };
for 'named', 'also-named' -> $n {
    f(|($n => rand))                # OUTPUT: «(:$named)␤(:$also-named)␤»
}

my $pair = :named(1);
f |$pair;                           # OUTPUT: «(:$named)␤»

같은 방법으로 Hash를 named 인자로 변환할 수 있어요.

sub f(:$also-named) { note &?ROUTINE.signature };
my %pairs = also-named => 4;
f |%pairs;                              # OUTPUT: «(:$also-named)␤»

리스트를 담은 Hash는 named 인자로 슬립될 때 문제가 될 수 있어요. 컨테이너의 여분의 겹을 피하려면 슬립 전에 Map으로 강제 변환하세요.

class C { has $.x; has $.y; has @.z };
my %h = <x y z> Z=> (5, 20, [1,2]);
say C.new(|%h.Map);
# OUTPUT: «C.new(x => 5, y => 20, z => [1, 2])␤»

named 인자에 원하는 만큼 많은 별칭을 만들 수 있어요:

sub alias-named(:color(:$colour),
                :variety(:style(:sort(:type(:class($kind)))))) {
    return $colour ~ " " ~ $kind
}
say alias-named(color => "red", style => "A");
say alias-named(colour => "green", variety => "B");
say alias-named(color => "white", class => "C");

익명 인자의 별칭으로 만들어 어떤 변수도 만들지 않는 named 인자를 만들 수 있어요. named 인자를 순전히 multi 후보를 선택하는 수단으로 쓸 때 유용한데, 트레잇 같은 데서 흔하지요:

# Timestamps calls to a routine.
multi trait_mod:<is>(Routine:D $r is raw, :timestamped($)!) {
    $r does my role timestamped { has Instant:D @.timestamps };
    $r.wrap: -> | { ENTER $r.timestamps.push: now; callsame };
}

sub foo is timestamped { }
foo;
say +&foo.?timestamps; # OUTPUT: «1␤»

선택적·필수 인자

Positional 파라미터는 기본적으로 필수이고, 기본값이나 뒤에 오는 물음표로 선택적으로 만들 수 있어요:

$sig1 = :(Str $id);         # required parameter
$sig2 = :($base = 10);      # optional parameter, default value 10
$sig3 = :(Int $x?);         # optional parameter, default is the Int type object

Named 파라미터는 기본적으로 선택적이고, 뒤에 오는 느낌표로 필수로 만들 수 있어요:

$sig1 = :(:%config);        # optional parameter
$sig2 = :(:$debug = False); # optional parameter, defaults to False
$sig3 = :(:$name!);         # mandatory 'name' named parameter

기본값은 이전 파라미터에 의존할 수 있고, (개념적으로는) 각 호출마다 새로 계산돼요.

$sig1 = :($goal, $accuracy = $goal / 100);
$sig2 = :(:$excludes = ['.', '..']);        # a new Array for every call

파라미터에 인자가 전달됐는가?

주어진 파라미터에 인자가 전달됐는지 확인하는 표예요:

파라미터 종류 예제 설명 인자 없음 확인
Slurpy *@array .defined로 확인하지 말 것 if not @array
Required $foo 생략 불가 (해당 없음)
Optional @bar = default 적절한 기본값 선택¹ if @bar === default

¹ WHICH 메서드로 확인할 수 있는, 뚜렷한 identity를 가진 Object가 적절한 기본값이에요.

기본값이 있는 파라미터는 항상 선택적이므로, ?로 표시할 필요가 없어요.

그다음 루틴 본문에서 === 값 identity 연산자로 이 정확한 기본 객체가 파라미터에 바인딩됐는지 확인할 수 있어요. 이 예제들은 ===가 호출하는 .WHICH 테스트가 ObjAt 타입 객체를 돌려준다는 것을 반영해 PositionalAt 같은 이름을 쓰고 있어요.

positional 파라미터 예제:

my constant PositionalAt = Positional.new;

sub a (@list = PositionalAt) { say @list === PositionalAt }
a;              # OUTPUT: «True␤»
a [1, 2, 3];    # OUTPUT: «False␤»

스칼라 파라미터 예제:

my constant AnyAt = Any.new;

sub b ($x=AnyAt, :$y=AnyAt) { say $x === AnyAt; say $y === AnyAt }
b 1;            # OUTPUT: «False␤True␤»
b 1, :2y;       # OUTPUT: «False␤False␤»

파라미터가 타입이 있다면, 타입 스마일리를 multi와 함께 이렇게 쓸 수 있어요:

multi c (Int:U $z)  { say 'Undefined' }
multi c (Int:D $z)  { say 'Defined'   }
multi c (Int   $z?) { say 'Omitted'   }
c;              #Omitted
c (Int);        #Undefined
c 42;           #Defined

동적 변수

동적 변수는 시그니처에서 허용되지만 특별한 동작을 제공하지는 않아요. 인자 바인딩이 어차피 두 스코프를 연결하니까요.

인자 분해 (Destructuring)

비스칼라 파라미터 뒤에 (또는 대신에) 괄호로 된 sub-signature가 올 수 있는데, 주어진 인자를 분해해요. 리스트의 분해는 그 요소들일 뿐이에요:

sub first(@array ($first, *@rest)) { $first }

또는:

sub first([$f, *@]) { $f }

해시의 분해는 그 페어들이에요:

sub all-dimensions(% (:length(:$x), :width(:$y), :depth(:$z))) {
    $x andthen $y andthen $z andthen True
}

Pointy 루프도 해시를 분해해서 변수에 할당할 수 있어요:

my %hhgttu = (:40life, :41universe, :42everything);
for %hhgttu -> (:$key, :$value) {
  say "$key → $value";
}
# OUTPUT: «universe → 41␤life → 40␤everything → 42␤»

일반적으로 객체는 그 속성에 따라 분해돼요. 흔한 관용구는 for 루프에서 Pair의 키와 값을 푸는 거예요:

for <Peter Paul Merry>.pairs -> (:key($index), :value($guest)) { }

하지만 객체를 속성으로 푸는 이 방식은 기본 동작일 뿐이에요. 객체를 다르게 분해하려면 그 Capture 메서드를 바꾸세요.

Sub-signatures (하위 시그니처)

복합 파라미터에 매칭하려면 인자 이름 뒤의 괄호 안에 sub-signature를 쓰면 돼요.

sub foo(\p(Int, Str)){
   put "called with {p.raku}"
};
foo((42, "answer"));
# OUTPUT: «called with (42, "answer")␤»

Sub-signature는 시그니처에 쓰이는 대부분의 문법을 스스로 쓸 수 있어요.

sub bar(\p(Int $y where * > 5, Str $s?, *%h)) { put p.raku; put $s // "undefined"; }
bar((42, life => 40, universe => 41));
# OUTPUT: «(42, :life(40), :universe(41))␤undefined␤»

Capture 파라미터

파라미터 앞에 세로 막대 |를 붙이면 그 파라미터가 Capture가 되어, 남은 positional·named 인자를 전부 사용해요.

sub a(Int $i, |cap) { say $i; say cap.gist }
a(42, universe => 41, 1, 2, 3);
# OUTPUT: ›42␤\(1, 2, 3, :universe(41))␤»

변수에 캡처된 인자는 슬립 연산자 |로 한꺼번에 전달할 수 있어요.

sub b(Int $i, Str $s) { say $i.^name ~ ' ' ~ $s.^name }
sub c(|cap) { say cap.^name; b(|cap) }
c(42, "answer");
# OUTPUT: «Capture␤Int Str␤»

Capture에 적용되는 인자를 sub-signature로 제약할 수도 있어요.

sub d(|cap(Int, Str, *%)) { put "called with {cap.raku}" };
d(41);
# OUTPUT: «Too few positionals passed to 'd'; expected 2 arguments but got 1 in sub-signature or parameter cap␤ ...»

바인딩되지 않은 Captureproto 정의(예: proto foo (|) {*})에서 루틴의 multi 정의가 어떤 타입 제약도 가질 수 있음을 나타내는 데 자주 쓰여요. proto 예제를 참고하세요.

;; 구분자

다중 디스패치에서 둘 이상의 multi 시그니처가 주어진 입력 파라미터와 매칭되면, 가장 좁은(narrowest) 시그니처가 이겨요.

시그니처에 이중 세미콜론 ;;이 있으면, 그 좁음(narrowness) 분석에서 ;; 왼쪽의 파라미터만 고려돼요. 콤마 자리를 대신하거나 맨 앞에 올 수 있어요:

multi foo (;; Numeric $a) { say "numeric" };
multi foo (;; Int     $a) { say "int" };
foo(42);
# OUTPUT: «Ambiguous call to 'foo(Int)'; these signatures all match:
#           (;; Numeric $a) from <unknown file> line 1
#           (;; Int $a) from <unknown file> line 1␤...»
# (Without the ;; the output would have been «int».)

이는 보통의 우선순위 순서가 바람직하지 않을 때 유용해요. named 파라미터가 있는 이 예제를 봐요:

multi bar(Int $i) { say "just $i" };
multi bar(Int $i, Str :$s) { say "both $i and 「$s.raku」" };
bar(42);
# OUTPUT: «Use of uninitialized value of type Str in string context.␤(...)
#           both 42 and 「」␤  in sub e at <tmp> line 1␤»

여기선 두 번째 multi가 실행됐어요. 그 시그니처가 첫 번째보다 좁고 실제로 bar(42)와 매칭되기 때문인데, $s는 정의되지 않은 채 남아 있음에도 그래요. ;;를 추가하면 그 우선순위를 첫 번째에 맞게 낮출 수 있어요:

multi baz(Int $i) { say "just $i" };
multi baz(Int $i;; Str :$s) { say "both $i and 「$s」" };
baz(42);
# OUTPUT: «Ambiguous call to 'baz(Int)'; these signatures all match:
#           (Int $i) from <tmp> line 1␤  (Int $i;; Str :$s) from <tmp> line 1␤ ...»

그다음 첫 multi에 트레잇 is default를 줄 수 있는데, 그 정확한 효과는 같은 우선순위의 시그니처 사이의 동률을 깨는 거예요.

시그니처의 좁음(그리고 그에 따른 우선순위)을 제어하는 방법은 하나만이 아니에요. 파라미터를 ?로 선택적 만들거나 !로 필수 만드는 것도 다른 옵션이에요.

이중 세미콜론 ;;은 파라미터화된 역할의 시그니처에서도 쓸 수 있어요. 파라미터화된 역할은 시그니처에서 생성된 소위 "긴 이름"을 갖는데, introspection에 쓸 수 있어요. ;; 왼쪽의 파라미터만 그것에 기여해요.

파라미터 트레잇과 수정자

기본적으로 파라미터는 인자에 바인딩되고 읽기 전용으로 표시돼요. 파라미터의 트레잇으로 바꿀 수 있어요.

is copy 트레잇은 인자를 복사하고, 루틴 안에서 수정할 수 있게 해 줘요.

sub count-up($x is copy) {
    $x = ∞ if $x ~~ Whatever;
    .say for 1..$x;
}

read-write를 뜻하는 is rw 트레잇은 파라미터가 변수(또는 다른 쓰기 가능 컨테이너)에 바인딩되게 해요. 파라미터에 할당하면 호출자 쪽 변수의 값이 바뀌어요.

sub swap($x is rw, $y is rw) {
    ($x, $y) = ($y, $x);
}

slurpy 파라미터에서 is rw는 언어 설계자들이 미래에 쓰기 위해 예약돼 있어요.

is raw 트레잇은 백슬래시나 플러스 기호를 "시길"로 해서 선언한 파라미터에 자동 적용되고, 보통 시길이 있는 파라미터가 그렇게 동작하게 만드는 데도 쓸 수 있어요. slurpy의 특수한 경우, 보통 Scalar로 가득한 Array를 만든다고 위에서 설명했는데, is raw는 대신 List를 만들게 해요. 그 리스트의 각 요소는 raw 파라미터로 직접 바인딩돼요.

읽기 전용 파라미터를 명시적으로 요청하려면 is readonly 트레잇을 써요. 이것은 컨테이너에만 적용된다는 점을 기억하세요. 안의 객체는 mutator 메서드를 잘 가질 수 있고, Raku는 객체의 속성에 불변성을 강제하지 않아요.

트레잇 뒤에는 where 절이 올 수 있어요:

sub ip-expand-ipv6($ip is copy where m:i/^<[a..f\d\:]>**3..39$/) { }