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»
콜론 페어는 쉼표 없이 이어 붙여 Pair의 List를 만들 수 있어요. 콜론 리스트를 할당할 때는 문맥에 따라 명시적으로 지정해 줘야 할 때도 있어요.
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»
여기서 짚고 넘어갈 점이 있어요. Scalar를 Pair의 값으로 할당하면, 그 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»
Pair는 Associative 역할을 구현하므로 연관 서브스크립트 연산자로 값에 접근할 수 있어요. 다만 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)
%topic이 Associative라면 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 Pair의 Bool 값과 비교해요. 예를 들어 소수 검사를 스마트매치로 할 수 있어요.
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의 .value가 Iterable이 아니라면 Seq는 Pair 하나만 담는데, 그 .key는 invocant의 .value이고 .value는 invocant의 .key예요.
:foo<bar>.invert.raku.say; # OUTPUT: «(:bar("foo"),).Seq»
invocant의 .value가 Iterable이라면 돌려주는 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»