Capture — 시그니처로 넘길 수 있는 인자 묶음

Capture — 시그니처로 넘길 수 있는 인자 묶음

함수를 호출할 때 넘기는 인자들, 즉 "호출부에서 건네는 값 묶음"을 하나의 객체로 다루고 싶을 때가 있어요. 그 역할을 하는 게 바로 Capture예요. 인자를 받는 쪽(시그니처)과 짝을 이루는 개념입니다.

출처: Raku 공식 문서 — Capture

본문

class Capture { }

Capture는 코드 객체에 인자를 넘기기 위한 컨테이너예요. Signature(시그니처)의 반대편 개념이라고 할 수 있어요. 즉 capture는 호출자가 정한 인자들이고, 시그니처는 피호출자가 정한 매개변수들이죠. 예를 들어 print $a, $b라고 호출하면 $a, $b 부분이 capture입니다.

캡처는 위치 인자를 위한 리스트 같은 부분과, 이름 인자를 위한 해시 같은 부분을 담고 있어서 PositionalAssociative처럼 동작하지만, 실제로 그 두 롤을 mixin 하지는 않아요. 다른 자료구조처럼 독립된 캡처를 만들어 저장하고 나중에 쓸 수 있습니다.

리터럴 Capture는 어떤 항(term) 앞에 백슬래시 \를 붙여 만들 수 있어요. 보통 그 항은 List인데, 여기서 key => value 형태나 :key<value> 형태의 Pair 리터럴은 이름 부분으로 들어가고, 나머지 항들은 모두 위치 부분으로 들어갑니다('key' => value 형태의 Pair도 위치 부분으로 들어가요).

my $a = \(42);                      # 위치 인자 하나를 가진 Capture
my $b = \(1, 2, verbose => True);   # 위치 인자 둘과 이름 인자 하나를 가진 Capture
my $c = \(1, 2, :verbose(True));    # 위와 같아요
my $c = \(1, 2, 'verbose' => True); # 위치 인자 셋을 가진 Capture

다시 강조하자면, 캡처의 이름 인자는 다음 두 가지 방식 중 하나로만 만들어야 해요.

  • 매개변수를 가리키는 따옴표 없는 키 다음에 =>, 그 다음에 값을 쓰는 방식. 예: by => {1/$_}.
  • 매개변수 이름을 딴 콜론-페어 리터럴을 쓰는 방식. 예: :into(my %leap-years).

예를 들어:

sub greet(:$name, :$age) {
    "$name, $age"
}

my $d = \(name => 'Mugen', age => 19);   # OK
my $e = \(:name('Jin'), :age(20));       # OK
my $f = \('name' => 'Fuu', 'age' => 15); # 안 됨, 키에 따옴표가 붙어 있어요

nameage라는 두 이름 인자를 받는 greet 서브루틴에서, $d$e 캡처는 잘 동작하지만 $f 캡처는 Too many positionals passed... 오류를 던져요. 'age' => 20은 (앞서 말한 두 방식 중 하나로 만든) 이름 인자가 아니라, greet이 전혀 기대하지 않는 위치 인자이기 때문이에요. 캡처 맥락에서는 따옴표가 붙은 키가 이름 인자를 만들지 않습니다. 'key' => value는 그냥 또 하나의 위치 매개변수일 뿐이라, 이름 인자로 캡처를 만들 때는 주의해야 해요.

캡처를 만들고 나면, 서브루틴 호출에서 세로 막대 |를 앞에 붙여 사용할 수 있어요. 그러면 캡처 안의 값들을 마치 직접 인자로 넘긴 것처럼 동작해요 — 이름 인자는 이름 인자로, 위치 인자는 위치 인자로 전달됩니다. 캡처는 다른 서브루틴에서든 몇 번이든 재사용할 수 있어요.

say greet |$d;                # OUTPUT: «Mugen, 19␤»
say greet |$e;                # OUTPUT: «Jin, 20␤»
my $x = \(4, 2, 3, -2);
say reverse |$x;              # OUTPUT: «(-2 3 2 4)␤»
say sort 5, |$x;              # OUTPUT: «(-2 2 3 4 5)␤»

say unique |$x, as => {.abs}; # OUTPUT: «(4 2 3)␤»
say unique |$x, :as({.abs});  # OUTPUT: «(4 2 3)␤»

my $y = \(1, 7, 3, by => {1/$_});
say min |$y;                  # OUTPUT: «7␤», min 1, 7, 3, by => {1/$_}과 같아요
say max |$y;                  # OUTPUT: «1␤», max 1, 7, 3, by => {1/$_}과 같아요

Signature 안에서는 시길리스(시길 없는) 매개변수 앞에 세로 막대 |를 붙여 Capture를 만들 수 있어요. 그러면 인자 목록의 나머지를 그 캡처 매개변수로 모아 줍니다.

sub f($a, |c) {
    say $a;
    say c;
    say c.^name;
    say c.list; # Methods 섹션 참고
    say c.hash; # Methods 섹션 참고
}

f 1, 2, 3, a => 4, :b(5);
# OUTPUT:
# 1
# \(2, 3, :a(4), :b(5))
# Capture
# (2 3)
# Map.new((a => 4, b => 5))

CaptureList처럼 컨테이너를 담을 수도 있다는 점을 기억하세요. 리터럴 값만 담는 게 아니에요.

my $b = 1;
my $c = \(4, 2, $b, 3);
say min |$c;        # OUTPUT: «1␤»
$b = -5;
say min |$c;        # OUTPUT: «-5␤»

메서드

method list

method list(Capture:D:)

Capture의 위치 부분을 반환해요.

my Capture $c = \(2, 3, 5, apples => (red => 2));
say $c.list; # OUTPUT: «(2 3 5)␤»

method hash

method hash(Capture:D:)

Capture의 이름/해시 부분을 반환해요.

my Capture $c = \(2, 3, 5, apples => (red => 2));
say $c.hash; # OUTPUT: «Map.new((:apples(:red(2))))␤»

method elems

method elems(Capture:D: --> Int:D)

Capture 안의 위치 요소 개수를 반환해요.

my Capture $c = \(2, 3, 5, apples => (red => 2));
say $c.elems; # OUTPUT: «3␤»

method keys

multi method keys(Capture:D: --> Seq:D)

모든 위치 키 다음에 모든 이름 키를 담은 Seq를 반환해요. 위치 인자의 키는 0부터 시작하는 각 인자의 서수 위치입니다.

my $capture = \(2, 3, 5, apples => (red => 2));
say $capture.keys; # OUTPUT: «(0 1 2 apples)␤»

method values

multi method values(Capture:D: --> Seq:D)

모든 위치 값 다음에 모든 이름 인자 값을 담은 Seq를 반환해요.

my $capture = \(2, 3, 5, apples => (red => 2));
say $capture.values; # OUTPUT: «(2 3 5 red => 2)␤»

method kv

multi method kv(Capture:D: --> Seq:D)

keysvalues가 번갈아 나오는 Seq를 반환해요. 있으면 위치 키와 값이 먼저 나오고, 그다음 이름 키와 값이 나옵니다.

my $capture = \(2, 3, apples => (red => 2));
say $capture.kv; # OUTPUT: «(0 2 1 3 apples red => 2)␤»

method pairs

multi method pairs(Capture:D: --> Seq:D)

모든 인자를, 위치 인자 먼저 이름 인자 나중에, PairSeq로 반환해요. 위치 인자는 0부터 시작하는 서수 값을 키로 갖고, 이름 인자는 자기 이름을 키로 가집니다.

my Capture $c = \(2, 3, apples => (red => 2));
say $c.pairs; # OUTPUT: «(0 => 2 1 => 3 apples => red => 2)␤»

method antipairs

multi method antipairs(Capture:D: --> Seq:D)

모든 인자를, 위치 인자 먼저 이름 인자 나중에, 키와 값을 서로 바꾼 PairSeq로 반환해요. 즉 값이 키가 되고 키가 값이 됩니다. pairs 메서드와 반대 동작이에요.

my $capture = \(2, 3, apples => (red => 2));
say $capture.antipairs; # OUTPUT: «(2 => 0 3 => 1 (red => 2) => apples)␤»

method Bool

method Bool(Capture:D: --> Bool:D)

Capture가 이름 인자나 위치 인자를 하나라도 담고 있으면 True를 반환해요.

say \(1,2,3, apples => 2).Bool; # OUTPUT: «True␤»
say \().Bool;                   # OUTPUT: «False␤»

method Capture

method Capture(Capture:D: --> Capture:D)

자기 자신, 즉 invocant를 반환해요.

say \(1,2,3, apples => 2).Capture; # OUTPUT: «\(1, 2, 3, :apples(2))␤»

method Numeric

method Numeric(Capture:D: --> Int:D)

Capture 안의 위치 요소 개수를 반환해요.

say \(1,2,3, apples => 2).Numeric; # OUTPUT: «3␤»