Perl 참조(reference)와 중첩 데이터 구조

Perl 참조(reference)와 중첩 데이터 구조

이 문서는 Perl 참조의 모든 측면을 다루는 완전한 공식 레퍼런스예요. 핵심 기능만 빠르게 익히고 싶다면 보다 짧은 튜토리얼인 perlreftut을 먼저 보는 걸 권해요.

출처: Perl 공식 문서 - perlref

본문

소개 (NOTE)

이 문서는 참조(reference)의 모든 측면에 대한 완전한 문서입니다. 꼭 필요한 핵심 기능만 다루는 짧은 튜토리얼 수준의 입문서는 perlreftut을 참고하세요.

설명 (DESCRIPTION)

Perl 5 이전에는 복잡한 데이터 구조를 표현하기가 어려웠어요. 모든 참조가 심볼릭(symbolic)이어야 했고, 그조차도 심볼 테이블 항목 대신 변수를 가리키기가 까다로웠기 때문이죠. 이제 Perl은 변수에 대한 심볼릭 참조를 쓰기 쉽게 만들어 줬을 뿐 아니라, 어떤 데이터나 코드 조각에 대한 하드 참조(hard reference) 도 허용해요. 어떤 스칼라든 하드 참조를 담을 수 있어요. 배열과 해시는 스칼라를 담으므로, 이제 배열의 배열, 배열의 해시, 해시의 배열, 함수들의 해시의 배열 등 원하는 대로 어렵지 않게 쌓아 올릴 수 있어요.

하드 참조는 영리해요. 참조 개수(reference count)를 알아서 추적하다가, 참조 대상의 참조 개수가 0이 되면 자동으로 해제하죠. (자기 참조 또는 순환 구조의 값들은 조금만 도와주지 않으면 참조 개수가 0까지 내려가지 않을 수 있어요. 자세한 설명은 "순환 참조"를 보세요.) 만약 그 대상이 객체라면 객체는 파괴(destruct)됩니다. 객체에 대한 더 자세한 내용은 perlobj를 보세요. (어떤 의미에서 Perl의 모든 것은 객체지만, 우리는 보통 클래스 패키지에 공식적으로 "bless"된 객체를 가리키는 참조에만 그 단어를 아껴 써요.)

심볼릭 참조는 변수나 다른 객체의 이름이에요. 마치 Unix 파일시스템의 심볼릭 링크가 파일의 이름만 담고 있는 것과 같죠. *glob 표기는 일종의 심볼릭 참조 역할을 해요. (심볼릭 참조를 "소프트 참조(soft reference)"라고 부르기도 하는데, 그렇게 부르지 말아 주세요. 쓸모없는 동의어까지 더하면 참조는 더 헷갈릴 뿐이거든요.)

반면 하드 참조는 Unix 파일시스템의 하드 링크에 더 가까워요. (다른) 이름이 무엇이든 신경 쓰지 않고 그 밑에 있는 객체에 접근하는 데 사용하죠. 아래 문단처럼 "참조"라는 단어를 수식어 없이 쓰면, 보통 하드 참조를 말하는 거예요.

Perl에서 참조는 쓰기 쉽습니다. 딱 하나의 지배적인 원칙만 기억하면 돼요: 일반적으로 Perl은 암묵적 참조 생성이나 역참조(dereference)를 하지 않는다. 스칼라가 참조를 담고 있더라도 항상 평범한 스칼라처럼 동작해요. 그게 배열이나 해시나 서브루틴으로 마법처럼 변하지 않아요. 직접 역참조해 달라고 명시적으로 알려 줘야 하죠.

참조 만들기 (Making References)

참조는 여러 방법으로 만들 수 있어요.

백슬래시 연산자 (Backslash Operator)

변수, 서브루틴, 값에 백슬래시 연산자를 쓰는 방법이에요. (C의 &(address-of) 연산자와 아주 비슷하게 동작해요.) 보통 이건 변수에 대한 또 다른 참조를 만들어요. 이미 심볼 테이블에 그 변수에 대한 참조가 있으니까요. 하지만 심볼 테이블 참조는 사라질 수 있는데, 백슬래시가 돌려준 참조는 남아 있어요. 몇 가지 예를 볼게요.

$scalarref = \$foo;
$arrayref  = \@ARGV;
$hashref   = \%ENV;
$coderef   = \&handler;
$globref   = \*foo;

백슬래시 연산자로는 IO 핸들(filehandle이나 dirhandle)에 대한 진짜 참조를 만들 수 없어요. 얻을 수 있는 최대치는 typeglob에 대한 참조인데, 이것은 사실 심볼 테이블 항목 전체예요. 하지만 아래에서 설명할 *foo{THING} 문법도 있으니 참고하세요. 어쨌든 type glob과 globref를 마치 IO 핸들처럼 사용할 수는 있어요.

대괄호 (Square Brackets)

대괄호로 익명 배열(anonymous array)에 대한 참조를 만들 수 있어요.

$arrayref = [1, 2, ['a', 'b', 'c']];

여기서 우리는 세 요소로 된 익명 배열에 대한 참조를 만들었어요. 그 마지막 요소는 다시 세 요소로 된 또 다른 익명 배열에 대한 참조죠. (아래에서 설명할 다차원 문법으로 접근할 수 있어요. 예를 들어 위 코드 다음에 $arrayref->[2][1]은 "b"라는 값을 가져요.)

열거된 리스트에 대한 참조를 취하는 것은 대괄호를 쓰는 것과 같지 않아요. 대신 그건 참조들의 리스트를 만드는 것과 같죠!

@list = (\$x, \@y, \%z);
@list = \($x, @y, %z);      # same thing!

특별한 경우로, \(@foo)@foo 자체에 대한 참조가 아니라 @foo의 내용물에 대한 참조들의 리스트를 반환해요. %foo도 마찬가지인데, 다만 키 참조는 복사본에 대한 것이라는 점만 달라요 (키는 완전한 스칼라가 아니라 그냥 문자열이므로).

중괄호 (Curly Brackets)

중괄호로 익명 해시(anonymous hash)에 대한 참조를 만들 수 있어요.

$hashref = {
    'Adam'  => 'Eve',
    'Clyde' => 'Bonnie',
};

이런 익명 해시·배열 컴포저는 자유롭게 섞어서 원하는 만큼 복잡한 구조를 만들 수 있어요. 아래에서 설명할 다차원 문법도 이런 것들에 적용돼요. 위의 값들은 리터럴이지만 변수나 표현식도 똑같이 잘 동작해요. Perl의 할당 연산자는 (local()이나 my() 안에서도) 컴파일 타임 선언이 아니라 실행 가능한 문장이기 때문이죠.

중괄호는 BLOCK 등 다른 여러 용도로도 쓰이므로, 문장 시작에서 중괄호 앞에 +return을 붙여서 여는 중괄호가 BLOCK을 시작하는 게 아니라는 걸 Perl에 알려 줘야 할 때가 있어요. 이 가끔 있는 번거로움보다 중괄호 사용의 간결함과 기억하기 쉬움이 더 값지다고 여겨지죠.

예를 들어 새 해시를 만들어 그 참조를 반환하는 함수를 원한다면 이렇게 선택할 수 있어요.

sub hashem {        { @_ } }   # silently wrong
sub hashem {       +{ @_ } }   # ok
sub hashem { return { @_ } }   # ok

반대로 다른 의미를 원한다면 이렇게 하면 돼요.

sub showem {        { @_ } }   # ambiguous (currently ok,
                               # but may change)
sub showem {       {; @_ } }   # ok
sub showem { { return @_ } }   # ok

앞에 붙는 +{{;는 항상 표현식을 HASH 참조 또는 BLOCK 중 하나로 구분해 주는 역할을 해요.

익명 서브루틴 (Anonymous Subroutines)

이름 없는 sub로 익명 서브루틴에 대한 참조를 만들 수 있어요.

$coderef = sub { print "Boink!\n" };

세미콜론에 주목하세요. 안쪽 코드가 즉시 실행되지 않는다는 점만 빼면 sub {}는 선언이라기보다는 do{}eval{} 같은 연산자예요. (단, eval("...") 안이 아니라면 그 줄을 아무리 여러 번 실행해도 $coderef는 여전히 같은 익명 서브루틴에 대한 참조를 가져요.)

익명 서브루틴은 my() 변수, 즉 현재 스코프 안에서 어휘적으로 보이는 변수에 대해 클로저(closure)처럼 동작해요. 클로저는 Lisp 세계에서 나온 개념인데, 특정 어휘적 문맥에서 익명 함수를 정의하면 그 함수가 문맥 밖에서 호출되더라도 그 문맥 안에서 실행되는 것처럼 행동한다는 뜻이에요.

인간의 말로 하자면, 서브루틴을 정의할 때와 호출할 때 양쪽 모두에서 인자를 넘기는 재미있는 방법이에요. 나중에 실행할 작은 코드 조각, 예를 들면 콜백(callback)을 세팅하는 데 유용하죠. 이걸로 객체 지향적인 짓도 할 수 있지만, Perl은 그걸 위한 별도 메커니즘을 이미 제공하고 있어요—perlobj를 보세요.

클로저를 eval()을 쓰지 않고 서브루틴 템플릿을 만드는 방법이라고 생각해도 돼요. 클로저가 어떻게 동작하는지 보여 주는 작은 예를 볼게요.

sub newprint {
    my $x = shift;
    return sub { my $y = shift; print "$x, $y!\n"; };
}
$h = newprint("Howdy");
$g = newprint("Greetings");

# Time passes...

&$h("world");
&$g("earthlings");

이건 이렇게 출력해요.

Howdy, world!
Greetings, earthlings!

특히 $x가 익명 서브루틴이 실행될 때쯤 "my $x"가 스코프를 벗어났음에도 불구하고 newprint()에 전달된 값을 계속 가리킨다는 점에 주목하세요. 그게 바로 클로저의 핵심이에요.

참고로 이건 어휘 변수에만 적용돼요. 동적 변수는 항상 해 왔던 대로 동작해요. 게다가 클로저는 대부분의 Perl 프로그래머가 처음부터 고민할 필요가 없는 그런 것이에요.

생성자 (Constructors)

참조는 생성자(constructor)라 불리는 특별한 서브루틴이 자주 반환해요. Perl 객체는 그냥 자신이 어떤 패키지와 연관되어 있는지 아는 특별한 타입의 객체에 대한 참조예요. 생성자는 그 연관을 만드는 방법을 아는 특별한 서브루틴일 뿐이죠. 평범한 참조로 시작해서, 객체이기도 하면서 계속 평범한 참조로 남도록 만들어요. 생성자는 보통 new()라고 이름 지어요. 간접적으로 호출할 있어요.

$objref = new Doggie( Tail => 'short', Ears => 'long' );

하지만 그건 어떤 경우엔 모호한 문법을 만들어낼 수 있어서, 직접 메서드 호출 방식을 쓰는 게 더 좋을 때가 많아요.

$objref   = Doggie->new(Tail => 'short', Ears => 'long');

use Term::Cap;
$terminal = Term::Cap->Tgetent( { OSPEED => 9600 });

use Tk;
$main    = MainWindow->new();
$menubar = $main->Frame(-relief              => "raised",
                        -borderwidth         => 2)

이 간접 객체(작성법) 구문은 use feature "indirect"가 적용돼 있을 때만 사용할 수 있어요. use v5.36 (또는 그 이상)을 요청하면 그렇지 않으므로, 간접 객체 구문은 아예 피하는 게 좋아요.

자동생성 (Autovivification)

적절한 타입의 참조가 존재한다고 가정하는 문맥에서 역참조하면 그 참조가 저절로 생겨날 수 있어요. 아직 역참조에 대해 이야기하지 않았으니, 지금은 예시를 보여 드릴 수가 없네요.

Typeglob 슬롯 (Typeglob Slots)

사랑스럽게도 *foo{THING} 문법이라 불리는 특별한 문법으로 참조를 만들 수 있어요. *foo{THING}은 *foo 안의 THING 슬롯에 대한 참조를 반환해요 (*foo는 foo로 알려진 모든 것을 담는 심볼 테이블 항목이죠).

$scalarref = *foo{SCALAR};
$arrayref  = *ARGV{ARRAY};
$hashref   = *ENV{HASH};
$coderef   = *handler{CODE};
$ioref     = *STDIN{IO};
$globref   = *foo{GLOB};
$formatref = *foo{FORMAT};
$globname  = *foo{NAME};    # "foo"
$pkgname   = *foo{PACKAGE}; # "main"

이들 대부분은 설명이 필요 없지만, *foo{IO}는 특별히 주목할 가치가 있어요. 이것은 IO 핸들을 반환하는데, 파일 핸들(perlfunc의 "open"), 소켓(perlfunc의 "socket""socketpair"), 디렉터리 핸들(perlfunc의 "opendir")에 쓰이죠. 이전 Perl 버전과의 호환을 위해 *foo{FILEHANDLE}*foo{IO}의 동의어로 쓰이긴 하지만, IO라는 하나의 이름을 일관되게 쓰도록 권장되므로 권장되진 않아요. v5.8과 v5.22 사이의 Perl에서는 deprecated 경고를 냈지만, 그 이후 이 폐기는 취소됐어요.

*foo{THING}은 해당 THING이 아직 사용되지 않았다면 undef를 반환해요. 단 스칼라의 경우는 예외예요. *foo{SCALAR}은 $foo가 아직 사용되지 않았다면 익명 스칼라에 대한 참조를 반환해요. 이것은 향후 릴리스에서 바뀔 수도 있어요.

*foo{NAME}*foo{PACKAGE}는 예외인데, 참조가 아니라 문자열을 반환하기 때문이에요. 이들은 typeglob 자체의 패키지와 이름을 반환하지, 그것에 할당된 것이 아니에요. 그래서 *foo=*Foo::bar 다음에 *foo는 문자열로 쓰면 "\*Foo::bar"가 되지만, *foo{PACKAGE}*foo{NAME}은 계속 각각 "main"과 "foo"를 내놓아요.

*foo{IO}perldata의 "Typeglobs and Filehandles"에 나온 *HANDLE 메커니즘의 대안이에요. 서브루틴 안팎으로 파일핸들을 넘기거나 더 큰 데이터 구조에 저장할 때 쓰죠. 단점은 새 파일핸들을 만들어 주지 않는다는 거예요. 장점은 typeglob 할당으로 원하는 것보다 더 많이 덮어쓸 위험이 적다는 거예요. (그래도 파일 핸들과 디렉터리 핸들을 여전히 하나로 합치긴 해요.) 하지만 아래 예처럼 들어오는 값을 typeglob이 아니라 스칼라에 할당하면 그런 위험은 없어요.

splutter(*STDOUT);          # pass the whole glob
splutter(*STDOUT{IO});      # pass both file and dir handles

sub splutter {
    my $fh = shift;
    print $fh "her um well a hmmm\n";
}

$rec = get_rec(*STDIN);     # pass the whole glob
$rec = get_rec(*STDIN{IO}); # pass both file and dir handles

sub get_rec {
    my $fh = shift;
    return scalar <$fh>;
}

참조 사용하기 (Using References)

참조 만드는 법은 여기까지예요. 이제는 오래 잃어버렸던 데이터에 다시 접근하기 위해 참조를 어떻게 쓰는지 궁금하시겠죠. 몇 가지 기본 방법이 있어요.

단순 스칼라 (Simple Scalar)

변수나 서브루틴 이름의 일부로 식별자(또는 식별자 체인)를 넣을 자리면 어디든, 그 식별자를 올바른 타입의 참조를 담은 단순 스칼라 변수로 바꿀 수 있어요.

$bar = $$scalarref;
push(@$arrayref, $filename);
$$arrayref[0] = "January";
$$hashref{"KEY"} = "VALUE";
&$coderef(1,2,3);
print $globref "output\n";

여기서 우리가 $arrayref[0]이나 $hashref{\"KEY\"}를 역참조하는 게 _아니라_는 점을 이해하는 게 중요해요. 스칼라 변수의 역참조는 키 조회보다 먼저 일어나요. 단순 스칼라 변수보다 복잡한 것은 아래의 방법 2나 3을 써야 해요. 하지만 "단순 스칼라"에는 그 자체로 방법 1을 재귀적으로 쓰는 식별자도 포함돼요. 그래서 다음은 "howdy"를 출력해요.

$refrefref = \\\"howdy";
print $$$$refrefref;

블록 (Block)

변수나 서브루틴 이름의 일부로 식별자(또는 식별자 체인)를 넣을 자리면 어디든, 그 식별자를 올바른 타입의 참조를 반환하는 BLOCK으로 바꿀 수 있어요. 다시 말해 앞선 예들은 이렇게 쓸 수 있다는 거예요.

$bar = ${$scalarref};
push(@{$arrayref}, $filename);
${$arrayref}[0] = "January";
${$hashref}{"KEY"} = "VALUE";
&{$coderef}(1,2,3);
$globref->print("output\n");  # iff IO::Handle is loaded

이 경우 중괄호를 쓰는 게 좀 어리석어 보이긴 하지만, BLOCK은 어떤 임의의 표현식이든 담을 수 있어요. 특히 첨자 표현식 같은 것도요.

&{ $dispatch{$index} }(1,2,3);      # call correct routine

$$x 같은 단순한 경우에 중괄호를 생략할 수 있으니, 사람들은 종종 역참조 기호들을 제대로 된 연산자로 착각하고 그 우선순위를 궁금해해요. 만약 정말 연산자라면 중괄호 대신 괄호를 쓸 수 있을 텐데, 그렇지 않아요. 아래의 차이를 고려해 보세요. case 0은 case 1의 단축형이지, case 2의 단축형이 아니에요.

$$hashref{"KEY"}   = "VALUE";       # CASE 0
${$hashref}{"KEY"} = "VALUE";       # CASE 1
${$hashref{"KEY"}} = "VALUE";       # CASE 2
${$hashref->{"KEY"}} = "VALUE";     # CASE 3

case 2는 %hashref라는 변수에 접근하는 것이지, $hashref를 통해 그것이 가리키는 해시를 역참조하는 게 아니라는 점에서 또 속임수를 발휘해요. 그건 case 3죠.

화살표 표기법 (Arrow Notation)

서브루틴 호출과 개별 배열 요소 조회는 방법 2를 쓰기엔 너무 자주 일어나요. 문법 설탕으로, 방법 2의 예들은 이렇게 쓸 수 있어요.

$arrayref->[0] = "January";   # Array element
$hashref->{"KEY"} = "VALUE";  # Hash element
$coderef->(1,2,3);            # Subroutine call

화살표의 왼쪽은 참조를 반환하는 어떤 표현식이든 될 수 있고, 이전 역참조도 포함돼요. 여기서 $array[$x]$array->[$x]와 _같지 않다_는 점에 유의하세요.

$array[$x]->{"foo"}->[0] = "January";

이건 앞서 언급했던, 참조가 lvalue 문맥에서 저절로 생겨날 수 있는 경우 중 하나예요. 이 문장 전에 $array[$x]는 정의되어 있지 않을 수 있어요. 그렇다면 {\"foo\"}를 조회할 수 있도록 자동으로 해시 참조로 정의돼요. 마찬가지로 $array[$x]->{\"foo\"}[0]을 조회할 수 있도록 자동으로 배열 참조로 정의되죠. 이 과정을 autovivification(자동생성)이라고 불러요.

여기서 한 가지 더. 화살표는 대괄호 첨자 사이에서는 생략할 수 있어서, 위 코드를 이렇게 줄일 수 있어요.

$array[$x]{"foo"}[0] = "January";

그리고 평범한 배열만 쓰는 극단적인 경우에는, C의 배열과 똑같은 다차원 배열을 만들어 주죠.

$score[$x][$y][$z] += 42;

음, 사실 C의 배열과 완전히 같진 않아요. C는 배열을 요청에 따라 키울 줄 모르지만 Perl은 하거든요.

객체 (Objects)

어떤 참조가 우연히 객체에 대한 참조라면, 참조된 것들에 접근하는 메서드가 있을 거예요. 그리고 객체의 메서드를 정의한 클래스 패키지 안이 아니라면, 그 메서드들을 쓰는 편이 좋아요. 다시 말해, 매우 좋은 이유 없이 객체의 캡슐화(encapsulation)를 어기지 말라는 거예요. Perl은 캡슐화를 강제하지 않아요. 우리는 여기서 전체주의자가 아니에요. 다만 기본적인 예의는 기대할게요.

기타 사용법 (Miscellaneous Usage)

문자열이나 숫자를 참조로 쓰면 위에서 설명한 대로 심볼릭 참조가 돼요. 참조를 숫자로 쓰면 그것이 메모리에서 저장된 위치를 나타내는 정수가 돼요. 이것으로 유용하게 할 수 있는 유일한 일은 두 참조를 숫자로 비교해서 같은 위치를 가리키는지 보는 거예요.

if ($ref1 == $ref2) {  # cheap numeric compare of references
    print "refs 1 and 2 refer to the same thing\n";
}

참조를 문자열로 쓰면 perlobj에서 설명한 패키지 bless를 포함한 참조 대상의 타입과, 16진수로 표현된 숫자 주소를 모두 산출해요. ref() 연산자는 참조가 가리키는 것의 타입만 반환하지, 주소는 반환하지 않아요. 사용법의 자세한 내용과 예는 perlfunc의 "ref"를 보세요.

bless() 연산자는 참조가 가리키는 객체를 객체 클래스로 기능하는 패키지와 연관시키는 데 쓸 수 있어요. perlobj를 보세요.

typeglob도 참조처럼 역참조할 수 있어요. 역참조 문법이 항상 원하는 참조의 타입을 나타내기 때문이죠. 그래서 ${*foo}${\\$foo}는 둘 다 같은 스칼라 변수를 가리켜요.

문자열 안에 서브루틴 호출을 끼워 넣는 트릭을 소개할게요.

print "My sub returned @{[mysub(1,2,3)]} that time.\n";

작동 방식은 이래요. 큰따옴표 문자열 안에서 @{...}를 만나면 블록으로 평가돼요. 그 블록은 mysub(1,2,3) 호출 결과를 담은 익명 배열에 대한 참조를 만들어요. 그래서 블록 전체가 배열에 대한 참조를 반환하고, 그걸 @{...}가 역참조해서 큰따옴표 문자열에 넣는 거죠. 이 묘기는 임의의 표현식에도 유용해요.

print "That yields @{[$n + 5]} widgets\n";

마찬가지로 스칼라에 대한 참조를 반환하는 표현식은 ${...}로 역참조할 수 있어요. 그래서 위 표현식은 이렇게도 쓸 수 있어요.

print "That yields ${\\($n + 5)} widgets\n";

순환 참조 (Circular References)

Perl에서 "순환 참조(circular reference)"를 만들 수 있는데, 이는 메모리 누수로 이어질 수 있어요. 순환 참조는 두 참조가 서로를 가리키는 참조를 포함할 때 일어나요. 이렇게요.

my $foo = {};
my $bar = { foo => $foo };
$foo->{bar} = $bar;

단일 변수로도 순환 참조를 만들 수 있어요.

my $foo;
$foo = \$foo;

이 경우 변수의 참조 개수는 결코 0에 도달하지 않고, 참조들은 결코 가비지 컬렉션되지 않아요. 그래서 메모리 누수가 생길 수 있죠.

Perl의 객체는 참조로 구현되므로, 객체로도 순환 참조를 만들 수 있어요. 각 노드가 부모와 자식 노드를 참조하는 TreeNode 클래스를 상상해 보세요. 부모가 있는 어떤 노드든 순환 참조의 일부가 돼요.

"약한 참조(weak reference)"를 만들어 순환 참조를 끊을 수 있어요. 약한 참조는 변수의 참조 개수를 증가시키지 않아서, 객체가 스코프를 벗어나 파괴될 수 있게 해 줘요. Scalar::Util 모듈이 내보내는 weaken 함수나, Perl 5.35.7부터는 builtin::weaken으로 직접 참조를 약화시킬 수 있어요.

첫 번째 예를 더 안전하게 만드는 방법을 볼게요.

use Scalar::Util 'weaken';

my $foo = {};
my $bar = { foo => $foo };
$foo->{bar} = $bar;

weaken $foo->{bar};

$foo에서 $bar로 가는 참조가 약화됐어요. $bar 변수가 스코프를 벗어나면 가비지 컬렉션돼요. 다음에 $foo->{bar} 키의 값을 보면 undef일 거예요.

이런 원격 조작은 헷갈릴 수 있으니 weaken 사용에 주의해야 해요. 먼저 스코프를 벗어날 변수의 참조를 약화시켜야 해요. 그렇게 하면 더 오래 사는 변수가 스코프를 벗어날 때까지 기대한 참조를 담고 있게 되죠.

심볼릭 참조 (Symbolic references)

참조가 정의되지 않았으면 필요에 따라 저절로 생겨난다고 했지만, 참조로 쓰이는 값이 이미 정의되어 있는데 하드 참조가 아닌 경우엔 어떻게 되는지는 말하지 않았어요. 그걸 참조로 쓰면 심볼릭 참조로 취급돼요. 즉 스칼라의 값이 (어쩌면) 익명 값에 대한 직접적인 연결이 아니라 변수의 _이름_으로 간주되는 거죠.

사람들이 자주 이렇게 동작하길 기대해요. 그래서 실제로 그렇게 동작해요.

$name = "foo";
$$name = 1;                 # Sets $foo
${$name} = 2;               # Sets $foo
${$name x 2} = 3;           # Sets $foofoo
$name->[0] = 4;             # Sets $foo[0]
@$name = ();                # Clears @foo
&$name();                   # Calls &foo()
$pack = "THAT";
$"${pack}::$name" = 5;    # Sets $THAT::foo without eval

이건 강력하고 약간 위험해요. (진심으로) 하드 참조를 쓰려다 우연히 심볼릭 참조를 쓰게 될 가능성이 있기 때문이죠. 그걸 막으려면 이렇게 할 수 있어요.

use strict 'refs';

그러면 포함하는 블록의 나머지 부분에서는 하드 참조만 허용돼요. 내부 블록은 이걸로 되돌릴 수 있어요.

no strict 'refs';

패키지 변수(지역화했더라도 전역)만 심볼릭 참조에 보여요. my()로 선언한 어휘 변수는 심볼 테이블에 없으므로 이 메커니즘에 보이지 않아요. 예를 들어:

local $value = 10;
$ref = "value";
{
    my $value = 20;
    print $$ref;
}

이건 여전히 20이 아니라 10을 출력해요. local()은 패키지 변수에 영향을 미치는데, 그것들은 모두 패키지에 "전역"이기 때문이에요.

그다지 심볼릭이 아닌 참조 (Not-so-symbolic references)

심볼릭 참조 주위의 대괄호는 그저 표현식 나머지에서 식별자나 변수 이름을 고립시키는 역할을 할 수 있어요. 문자열 안에서 항상 그래 왔듯이요. 예를 들어,

$push = "pop on ";
print "${push}over";

이건 push가 예약어임에도 불구하고 항상 "pop on over"를 출력하도록 되어 있어요. 이것은 큰따옴표가 없는 곳에서도 똑같이 동작하도록 일반화됐어요. 그래서

print ${push} . "over";

심지어

print ${ push } . "over";

도 같은 결과를 내요. 이 구조는 strict refs를 쓰고 있을 때 심볼릭 참조로 간주되지 않아요.

use strict 'refs';
${ bareword };      # Okay, means $bareword.
${ "bareword" };    # Error, symbolic reference.

비슷하게, 밑줄 없는 단어로 하는 첨자 표기가 많아서, 해시를 첨자로 인덱싱할 때 쓰는 어떤 bareword에도 같은 규칙이 적용돼요. 그래서 이제

$hash{ "aaa" }{ "bbb" }{ "ccc" }

라고 쓰는 대신 그냥

$hash{ aaa }{ bbb }{ ccc }

라고 쓰면 되고, 첨자가 예약어인지 걱정할 필요가 없어요. 드물게

$hash{ shift }

같은 걸 하고 싶을 땐, bareword 이상이 되게 하는 어떤 것이든 더해서 예약어로 해석되게 강제할 수 있어요.

$hash{ shift() }
$hash{ +shift }
$hash{ shift @_ }

use warnings 프래그마나 -w 스위치는 예약어를 문자열로 해석하면 경고해 줘요. 하지만 소문자 단어에 대해서는 더 이상 경고하지 않아요. 문자열이 사실상 인용되기 때문이죠.

사이비 해시(Pseudo-hashes): 배열을 해시로 쓰기

사이비 해시는 Perl에서 제거됐어요. fields 프래그마는 계속 사용할 수 있어요.

함수 템플릿 (Function Templates)

위에서 설명했듯이, 그 함수가 컴파일될 때 보이던 어휘 변수에 접근할 수 있는 익명 함수는 클로저를 만들어요. 그것은 나중에, 예를 들면 시그널 핸들러나 Tk 콜백에서 실행될 때까지도 그 변수들에 대한 접근을 유지해요.

클로저를 함수 템플릿으로 쓰면 비슷하게 동작하는 많은 함수를 생성할 수 있어요. 각각의 색상에 대해 HTML 폰트 변경을 만드는, 색깔 이름을 딴 함수들을 원한다고 가정해 보세요.

print "Be ", red("careful"), "with that ", green("light");

red()와 green() 함수는 비슷할 거예요. 이것들을 만들려면, 우리가 만들려는 함수의 이름을 가진 typeglob에 클로저를 할당할 거예요.

@colors = qw(red blue green yellow orange purple violet);
for my $name (@colors) {
    no strict 'refs';       # allow symbol table manipulation
    *$name = *{uc $name} = sub { "<FONT COLOR='$name'>@_</FONT>" };
}

이제 이 모든 서로 다른 함수들이 독립적으로 존재하는 것처럼 보여요. red(), RED(), blue(), BLUE(), green() 등을 호출할 수 있어요. 이 기법은 컴파일 시간과 메모리 사용을 모두 아끼고, 문법 검사가 컴파일 시간에 일어나므로 오류 가능성도 줄여 줘요. 익명 서브루틴 안의 어떤 변수든 제대로 된 클로저를 만들려면 어휘 변수여야 하는 게 중요해요. 그래서 루프 반복 변수에 my를 쓰는 거예요.

클로저에 프로토타입을 주는 게 정말 말이 되는 아주 드문 곳 중 하나가 여기예요. 이 함수들의 인자에 스칼라 문맥을 강제하고 싶다면 (이 특정 예에선 아마 현명한 생각은 아니지만), 이렇게 쓸 수도 있었을 거예요.

*$name = sub ($) { "<FONT COLOR='$name'>$_[0]</FONT>" };

하지만 프로토타입 검사는 컴파일 시간에 일어나므로, 위의 할당은 너무 늦게 일어나 쓸모가 없어요. 할당 루프 전체를 BEGIN 블록 안에 넣어 컴파일 동안 강제로 실행되게 하면 해결할 수 있어요.

시간이 지나며 변하는 어휘 변수에 대한 접근--위 for 루프의 것처럼 기본적으로 주변 어휘 스코프의 요소들에 대한 별칭(alias)--은 이름 있는 서브루틴이 아니라 오직 익명 서브루틴에서만 동작해요. 일반적으로 말해 이름 있는 서브루틴은 제대로 중첩되지 않으므로 main 패키지 스코프에서만 선언해야 해요.

이유는 이름 있는 서브루틴은 컴파일 시간에 생성되어, 부모 블록을 처음 실행할 때 그 어휘 변수들이 부모 어휘에 할당되기 때문이에요. 부모 스코프가 두 번째로 진입되면 그 어휘가 다시 생성되는데, 중첩된 서브루틴은 여전히 옛 변수들을 참조해요.

익명 서브루틴은 sub 연산자를 실행할 때마다, 그 자리에서 생성되므로 매번 캡처할 수 있어요. 다른 언어에서 자기만의 비공개 변수를 가진 중첩 서브루틴에 익숙하다면, Perl에서는 좀 애를 먹어야 할 거예요. 이런 종류의 코딩을 직관적으로 하면 위에서 설명한 이유 때문에 "will not stay shared"라는 수수께끼 같은 경고가 나요. 예를 들어, 이건 동작하지 않아요.

sub outer {
    my $x = $_[0] + 35;
    sub inner { return $x * 19 }   # WRONG
    return $x + inner();
}

해결책은 이래요.

sub outer {
    my $x = $_[0] + 35;
    local *inner = sub { return $x * 19 };
    return $x + inner();
}

이제 inner()는 익명 서브루틴의 임시 할당 때문에 outer() 안에서만 호출할 수 있어요. 하지만 호출할 때는 outer()가 호출된 시점의 outer() 스코프에서 온 어휘 변수 $x에 정상적으로 접근해요.

이것은 한 함수에 국한된 함수를 만드는 흥미로운 효과를 내는데, Perl에서는 보통 지원되지 않는 것이에요.

후위 역참조 문법 (Postfix Dereference Syntax)

v5.20.0부터 참조를 쓸 수 있는 후위(postfix) 문법이 제공돼요. 이 문법은 "참조 사용하기"에서 설명한 대로 동작하지만, 접두사 기호(sigil) 대신 접미사 기호-별표를 사용해요.

예를 들어:

$r = \@a;
@b = $r->@*; # equivalent to @$r or @{ $r }

$r = [ 1, [ 2, 3 ], 4 ];
$r->[1]->@*;  # equivalent to @{ $r->[1] }

$aref->@* = (1, 2, 3);  # same as @{ $aref } = (1, 2, 3)

Perl 5.20과 5.22에서는 이 문법을 use feature 'postderef'로 활성화해야 해요. Perl 5.24부터는 기능 선언이 없어도 사용할 수 있어요.

후위 역참조는 블록(둘러싸는) 역참조가 동작했던 모든 상황에서 동작해야 하고, 완전히 동등해야 해요. 이 문법은 역참조를 완전히 왼쪽에서 오른쪽으로 쓰고 읽을 수 있게 해 줘요. 다음 동등 관계가 정의돼요.

$sref->$*;  # same as  ${ $sref }
$aref->@*;  # same as  @{ $aref }
$aref->$#*; # same as $#{ $aref }
$href->%*;  # same as  %{ $href }
$cref->&*;  # same as  &{ $cref }
$gref->**;  # same as  *{ $gref }

특히 $cref->&*$cref->()동등하지 않고, 다른 목적으로 쓰일 수 있다는 점에 주의하세요.

후위 역참조 기능을 통해 glob 요소를 추출할 수 있어요.

$gref->*{SCALAR}; # same as *{ $gref }{SCALAR}

후위 배열·스칼라 역참조는 보간 문자열(큰따옴표나 qq 연산자)에서 수 있는데, 단 postderef_qq 기능이 활성화된 경우에만 그래요. 후위 배열 최고 인덱스 접근(->$#*)의 보간도 postderef_qq 기능이 켜져 있을 때 지원돼요.

후위 참조 슬라이싱 (Postfix Reference Slicing)

배열과 해시의 값 슬라이스도 후위 역참조 표기법으로 취할 수 있어요. 다음 동등 관계가 정의돼요.

$aref->@[ ... ];  # same as @$aref[ ... ]
$href->@{ ... };  # same as @$href{ ... }

5.20.0에서 추가되고 perldata의 키/값 해시 슬라이스 절에 문서화된 후위 키/값 쌍 슬라이싱도 기대한 대로 동작해요.

$aref->%[ ... ];  # same as %$aref[ ... ]
$href->%{ ... };  # same as %$href{ ... }

후위 배열과 마찬가지로, 후위 값 슬라이스 역참조도 postderef_qq 기능이 활성화된 경우에만 보간 문자열(큰따옴표나 qq 연산자)에서 쓸 수 있어요.

참조에 할당하기 (Assigning to References)

v5.22.0부터 참조 연산자에 할당할 수 있어요. 이것은 별칭(aliasing) 연산을 수행해서, 왼쪽에 참조된 변수 이름이 오른쪽에 참조된 대상의 별칭이 돼요.

\$x = \$y; # $x and $y now point to the same scalar
\&foo = \&bar; # foo() now means bar()

이 문법은 use feature 'refaliasing'으로 활성화해야 해요. 실험적이며, no warnings 'experimental::refaliasing'이 적용되지 않는 한 기본적으로 경고해요.

이러한 형식들에 할당할 수 있고, 오른쪽을 스칼라 문맥에서 평가하게 해요.

\$scalar
\@array
\%hash
\&sub
\my $scalar
\my @array
\my %hash
\state $scalar # or @array, etc.
\our $scalar   # etc.
\local $scalar # etc.
\local our $scalar # etc.
\$some_array[$index]
\$some_hash{$key}
\local $some_array[$index]
\local $some_hash{$key}
condition ? \$this : \$that[0] # etc.

슬라이싱 연산과 괄호는 오른쪽을 리스트 문맥에서 평가하게 해요.

\@array[5..7]
(\@array[5..7])
\(@array[5..7])
\@hash{'foo','bar'}
(\@hash{'foo','bar'})
\(@hash{'foo','bar'})
(\$scalar)
\($scalar)
\(my $scalar)
\my($scalar)
(\@array)
(\%hash)
(\&sub)
\(&sub)
\($foo, @bar, %baz)
(\$foo, \@bar, \%baz)

오른쪽의 각 요소는 올바른 타입의 데이터에 대한 참조여야 해요. 배열을 (그리고 어쩌면 my/state/our/local도) 바로 둘러싸는 괄호는 배열의 각 요소를 오른쪽에 참조된 대응 스칼라의 별칭으로 만들 거예요.

\(@x) = \(@y); # @x and @y now have the same elements
\my(@x) = \(@y); # likewise
\(my @x) = \(@y); # likewise
push @x, 3; # but now @x has an extra element that @y lacks
\(@x) = (\$x, \$y, \$z); # @x now contains $x, $y, and $z

그 형식을 local과 결합하고 해시를 바로 괄호로 둘러싸는 것은 금지돼요 (무엇을 해야 할지 명확하지 않기 때문이에요).

\local(@array) = foo(); # WRONG
\(%hash)       = bar(); # WRONG

참조와 비참조에 대한 할당은 리스트와 조건부 삼항 표현식에서 결합할 수 있어요. 오른쪽 값이 왼쪽의 각 요소에 대해 올바른 타입이기만 하면 되죠. 다만 이렇게 하면 코드가 난해해질 수 있어요.

(my $tom, \my $dick, \my @harry) = (\1, \2, [1..3]);
# $tom is now \1
# $dick is now 2 (read-only)
# @harry is (1,2,3)

my $type = ref $thingy;
($type ? $type eq 'ARRAY' ? \@foo : \$bar : $baz) = $thingy;

리스트 문맥에서의 참조 할당은 각 왼쪽 값에 대한 참조들의 리스트를 반환해요. 예를 들어

@b = ((\$l1, \$l2, \(@a)) = (\$r1, \$r2, $\r3, \$4));

는 이와 동등해요.

(\$l1, \$l2, \(@a)) = (\$r1, \$r2, $\r3, \$4);
@b = (\$l1, \$l2, \$a[0], \$a[1]);

foreach 루프도 루프 변수로 참조 생성자를 받을 수 있지만, 문법은 다음 중 하나로 제한되고 백슬래시 뒤에 선택적으로 my, state, our를 쓸 수 있어요.

\$s
\@a
\%h
\&c

괄호는 허용되지 않아요. 이 기능은 배열의 배열이나 배열의 해시에 특히 유용해요.

foreach \my @a (@array_of_arrays) {
    frobnicate($a[0], $a[-1]);
}

foreach \my %h (@array_of_hashes) {
    $h{gelastic}++ if $h{type} eq 'funny';
}

주의 (CAVEAT): 별칭은 클로저와 제대로 동작하지 않아요. 내부 서브루틴이나 eval에서 어휘 변수를 별칭하려 하면, 그 별칭은 내부 서브루틴 안에서만 보이고, 변수가 선언된 바깥 서브루틴에는 영향을 주지 않아요. 이 기이한 동작은 변경될 수 있어요.

변수에 대한 참조 선언 (Declaring a Reference to a Variable)

v5.26.0부터 참조 연산자는 my, state, our, local 뒤에 올 수 있어요. 이 문법은 use feature 'declared_refs'로 활성화해야 해요. 실험적이며, no warnings 'experimental::refaliasing'이 적용되지 않는 한 기본적으로 경고해요.

이 기능은 이것들을:

my \$x;
our \$y;

이것들과 동등하게 만들어요:

\my $x;
\our $x;

주로 위의 "참조에 할당하기"에서의 참조 할당에 쓰려고 만든 거예요. 또한 선언된 변수 리스트 중 일부 항목에만 백슬래시를 쓸 수 있게 해 줘요.

my ($foo, \@bar, \%baz); # equivalent to:  my $foo, \my(@bar, %baz);

경고: 참조를 해시 키로 쓰지 마세요

참조를 해시의 키로 (유용하게) 쓸 수 없어요. 문자열로 변환될 거예요.

$x{ \$x } = $x;

키를 역참조하려고 하면 하드 역참조가 되지 않고, 의도한 걸 이루지 못해요. 이런 비슷한 걸 하는 게 나을지도 몰라요.

$r = \@a;
$x{ $r } = $r;

그러면 적어도 keys()가 아니라 values()를 쓸 수 있는데, values()는 실제 참조니까요.

표준 Tie::RefHash 모듈이 이에 대한 편리한 해결책을 제공해요.

더 알아보기 (SEE ALSO)

명백한 문서들 말고도 소스 코드가 가르쳐 주는 게 있어요. 참조 사용의 병리학적 예시는 Perl 소스 디렉터리의 t/op/ref.t 회귀 테스트에서 찾을 수 있어요.

참조로 복잡한 데이터 구조를 만드는 방법은 perldscperllol을, 참조로 객체를 만드는 방법은 perlootutperlobj를 함께 보세요.

더 알아보기