Pair 타입

Pair 타입 (raku-type-pair)

키(key)와 값(value), 두 부분으로 이루어진 타입이에요. Hash의 원자 단위라고 볼 수 있고, 이름 인자·이름 파라미터와도 함께 쓰여요. Raku에서 => (fat arrow)로 값을 짝지은 것, 바로 그게 Pair예요.

출처: Raku Docs - Pair

정의

class Pair does Associative {}

Pair를 만드는 문법은 정말 많아요. 어느 걸 써도 결과는 같아요.

Pair.new('key', 'value'); # The canonical way
'key' => 'value';         # this...
:key<value>;              # ...means the same as this
:key<value1 value2>;      # But this is  key => <value1 value2>
:foo(127);                # short for  foo => 127
:127foo;                  # the same   foo => 127

마지막 형식은 유니코드 숫자도 지원해요.

# use MATHEMATICAL DOUBLE-STRUCK DIGIT THREE
say (:𝟛math-three);         # OUTPUT: «math-three => 3␤»

다만 숫자와 추가 유니코드 결합 부호로 합성된(synthetic) 형태는 안 돼요.

say :7̈a

식별자처럼 생긴 리터럴을 키로 쓸 수도 있어요. 일반 식별자 문법만 따르면 따옴표가 필요 없어요.

(foo => 127)              # the same   foo => 127

이것의 변형으로는 이런 게 있어요.

:key;                     # same as   key => True
:!key;                    # same as   key => False

그리고 루틴 호출에서 쓰는 또 다른 변형이 있어요.

sub colon-pair( :$key-value ) {
    say $key-value;
}
my $key-value = 'value';
colon-pair( :$key-value );               # OUTPUT: «value␤»
colon-pair( key-value => $key-value );   # OUTPUT: «value␤»

콜론 페어는 쉼표 없이 이어 붙여 PairList를 만들 수 있어요. 콜론 리스트를 할당할 때는 문맥에 따라 명시적으로 지정해 줘야 할 때도 있어요.

sub s(*%h){ say %h.raku };
s :a1:b2;
# OUTPUT: «{:a1, :b2}␤»

my $manna = :a1:b2:c3;
say $manna.^name;
# OUTPUT: «Pair␤»

$manna = (:a1:b2:c3);
say $manna.^name;
# OUTPUT: «List␤»

어떤 변수든 그 이름과 값을 Pair로 바꿀 수 있어요.

my $bar = 10;
my $p   = :$bar;
say $p; # OUTPUT: «bar => 10␤»

여기서 짚고 넘어갈 점이 있어요. ScalarPair의 값으로 할당하면, 그 Pair는 값 자체가 아니라 그 값을 담은 컨테이너를 들고 있어요. 그래서 Pair 바깥에서 값을 바꿀 수 있어요.

my $v = 'value A';
my $pair = a => $v;
$pair.say;  # OUTPUT: «a => value A␤»

$v = 'value B';
$pair.say;  # OUTPUT: «a => value B␤»

이 동작은 Pair를 어떻게 만들었는지(new를 썼는지, 콜론을 썼는지, fat arrow를 썼는지)나 Pair가 변수에 바인딩됐는지와는 전혀 무관해요. 이 동작을 바꾸고 싶다면 freeze 메서드로 Pair가 스칼라 컨테이너를 떼어내고 실제 값 자체를 들고 있게 만들면 돼요.

my $v = 'value B';
my $pair = a => $v;
$pair.freeze;
$v = 'value C';
$pair.say; # OUTPUT: «a => value B␤»

PairAssociative 역할을 구현하므로 연관 서브스크립트 연산자로 값에 접근할 수 있어요. 다만 Pair는 원소가 하나뿐이라 키가 그 키일 때만 값을 돌려주고, 그 외 키에는 Nil을 돌려줘요. :exists 같은 서브스크립트 부사도 쓸 수 있어요.

my $pair = a => 5;
say $pair<a>;           # OUTPUT: «5␤»
say $pair<a>:exists;    # OUTPUT: «True␤»
say $pair<no-such-key>; # OUTPUT: «Nil␤»

메서드

method new

multi method new(Pair: Mu  $key, Mu  $value)
multi method new(Pair: Mu :$key, Mu :$value)

Pair 객체를 만들어요.

method ACCEPTS

multi method ACCEPTS(Pair:D $: %topic)
multi method ACCEPTS(Pair:D $: Pair:D $topic)
multi method ACCEPTS(Pair:D $: Mu $topic)

%topicAssociative라면 invocant의 키로 그 안의 값을 찾아, invocant의 값이 그 값(.ACCEPTS)이 되는지 확인해요.

say %(:42a) ~~ :42a; # OUTPUT: «True␤»
say %(:42a) ~~ :10a; # OUTPUT: «False␤»

$topic이 또 다른 Pair라면 invocant의 키와 값이 각각 $topic의 키와 값(.ACCEPTS)이 되는지 확인해요.

say :42a ~~ :42a; # OUTPUT: «True␤»
say :42z ~~ :42a; # OUTPUT: «False␤»
say :10a ~~ :42a; # OUTPUT: «False␤»

$topic이 다른 아무 값이라면, invocant Pair의 키를 메서드 이름으로 취급해요. 그 메서드를 $topic에 호출하고, 그 Bool 결과를 invocant PairBool 값과 비교해요. 예를 들어 소수 검사를 스마트매치로 할 수 있어요.

say 3 ~~ :is-prime;             # OUTPUT: «True␤»
say 3 ~~  is-prime => 'truthy'; # OUTPUT: «True␤»
say 4 ~~ :is-prime;             # OUTPUT: «False␤»

이 형식은 Junction을 써서 같은 객체(IO::Path 같은)의 여러 메서드 Bool 값을 한 번에 확인할 때도 쓸 수 있어요.

say "foo" .IO ~~ :f & :rw; # OUTPUT: «False␤»
say "/tmp".IO ~~ :!f;      # OUTPUT: «True␤»
say "."   .IO ~~ :f | :d;  # OUTPUT: «True␤»

method antipair

method antipair(Pair:D: --> Pair:D)

키와 값을 맞바꾼 새 Pair 객체를 돌려줘요.

my $p = (d => 'Raku').antipair;
say $p.key;         # OUTPUT: «Raku␤»
say $p.value;       # OUTPUT: «d␤»

method key

multi method key(Pair:D:)

Pair 부분을 돌려줘요.

my $p = (Raku => "d");
say $p.key; # OUTPUT: «Raku␤»

method value

multi method value(Pair:D:) is rw

Pair 부분을 돌려줘요. is rw라서 수정도 가능해요.

my $p = (Raku => "d");
say $p.value; # OUTPUT: «d␤»

infix cmp

multi infix:<cmp>(Pair:D, Pair:D)

타입 무관 비교 연산자로 두 Pair를 비교해요. 먼저 부분을 비교하고, 키가 같으면 부분을 비교해요.

my $a = (Apple => 1);
my $b = (Apple => 2);
say $a cmp $b; # OUTPUT: «Less␤»

method fmt

multi method fmt(Pair:D: Str:D $format --> Str:D)

형식 문자열을 받아 Pair 부분을 그 형식대로 포맷한 문자열을 돌려줘요. 예를 들면 이렇게요.

my $pair = :Earth(1);
say $pair.fmt("%s is %.3f AU away from the sun")
# OUTPUT: «Earth is 1.000 AU away from the sun␤»

형식 문자열에 대한 자세한 내용은 sprintf 문서를 보세요.

method kv

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

Pair을 그 순서대로 담은 두 요소 Seq를 돌려줘요. Hash의 같은 이름 메서드(모든 항목을 키·값 목록으로 돌려주는)의 특수한 경우예요.

my $p = (Raku => "d");
say $p.kv[0]; # OUTPUT: «Raku␤»
say $p.kv[1]; # OUTPUT: «d␤»

method pairs

multi method pairs(Pair:D:)

바로 이 Pair 하나만 담은 리스트를 돌려줘요.

my $p = (Raku => "d");
say $p.pairs.^name; # OUTPUT: «List␤»
say $p.pairs[0];    # OUTPUT: «Raku => d␤»

method antipairs

multi method antipairs(Pair:D:)

invocant의 antipair를 담은 List를 돌려줘요.

my $p = (d => 'Raku').antipairs;
say $p.^name;                                     # OUTPUT: «List␤»
say $p.first;                                     # OUTPUT: «Raku => d␤»
say $p.first.^name;                               # OUTPUT: «Pair␤»

method invert

method invert(Pair:D: --> Seq:D)

Seq를 돌려줘요. invocant의 .valueIterable아니라면 SeqPair 하나만 담는데, 그 .key는 invocant의 .value이고 .value는 invocant의 .key예요.

:foo<bar>.invert.raku.say; # OUTPUT: «(:bar("foo"),).Seq␤»

invocant의 .valueIterable이라면 돌려주는 Seq.value의 항목 수만큼 Pair를 담아요. 각 항목이 그 Pair들의 .key가 되고 invocant의 .key가 그 .value가 돼요.

:foo<Raku is great>.invert.raku.say;
# OUTPUT: «(:Raku("foo"), :is("foo"), :great("foo")).Seq␤»

:foo{ :42a, :72b }.invert.raku.say;
# OUTPUT: «((:a(42)) => "foo", (:b(72)) => "foo").Seq␤»

정확히 .key.value를 맞바꾸려면 .antipair 메서드를 쓰세요.

method keys

multi method keys(Pair:D: --> List:D)

invocant의 키를 담은 List를 돌려줘요.

say (Raku => "d").keys;                           # OUTPUT: «(Raku)␤»

method values

multi method values(Pair:D: --> List:D)

invocant의 값을 담은 List를 돌려줘요.

say (Raku => "d").values;                         # OUTPUT: «(d)␤»

method freeze

method freeze(Pair:D:)

Pair을 읽기 전용으로 만들어요. Scalar 컨테이너에서 꺼내고 그 값을 돌려줘요.

my $str = "apple";
my $p = Pair.new('key', $str);
$p.value = "orange";              # this works as expected
$p.say;                           # OUTPUT: «key => orange␤»
$p.freeze.say;                    # OUTPUT: «orange␤»
$p.value = "a new apple";         # Fails
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: «X::Assignment::RO: Cannot modify an immutable Str (apple)␤»

참고: 이 메서드는 6.d 언어 버전부터 지원 중단(deprecated)됐어요. 대신 디컨테이너라이즈된(컨테이너가 벗겨진) 키/값으로 새 Pair를 만들어 쓰세요.

$p.=Map.=head.say;                                    # OUTPUT: «orange␤»

method Str

multi method Str(Pair:D: --> Str:D)

invocant를 key ~ 탭 ~ value 형식으로 포맷한 문자열 표현을 돌려줘요.

my $b = eggs => 3;
say $b.Str;                                       # OUTPUT: «eggs  3␤»

method Pair

method Pair()

invocant Pair 객체를 돌려줘요.

my $pair = eggs => 3;
say $pair.Pair === $pair;                         # OUTPUT: «True␤»