-
- 1. perlref
-
-
- 1.1. NAME
-
- 1.2. NOTE
-
- 1.3. DESCRIPTION
-
-
- 1.3.1. Making References
-
- 1.3.2. Using References
-
- 1.3.3. Symbolic references
-
- 1.3.4. Not-so-symbolic references
-
- 1.3.5. Pseudo-hashes: Using an array as a hash
-
- 1.3.6. Function Templates
-
1.4. WARNING
-
- 1.5. SEE ALSO
-
2. 기타
-
-
- 2.1. autovivification
-
- 2.2. autovivification 과 서브루틴 파라메터
-
- 2.3. 객체 메쏘드의 레퍼런스
-
- 2.4. 종종 저지르는 실수들
-
3. 기타
-
- 4. Comments
-
1. perlref
시간 날 때 perlref 문서를 요약해보려고 맘만 먹고, 시간이 안 나서 못 하고 있음;
1.3. DESCRIPTION
1.3.1. Making References
1.3.2. Using References
1.3.3. Symbolic references
1.3.4. Not-so-symbolic references
1.3.5. Pseudo-hashes: Using an array as a hash
1.3.6. Function Templates
1.5. SEE ALSO
2.1. autovivification
2.2. autovivification 과 서브루틴 파라메터
아직 값이 정의되지 않은 변수를, 해시 또는 배열의 레퍼런스인 것처럼 원소의 값을 읽거나 쓰려고 시도하면 자동으로 그 변수에 익명 해시 또는 익명 레퍼런스를 생성하여 할당해준다. 이것을 autovivification이라고 한다.
use Data::Dumper;
my $ref1;
$ref1->{key} = "value";
print Dumper($ref1), "\n";
my $ref2;
my $val2 = $ref2->{key};
print Dumper($ref2), "\n";
my $ref3;
$ref3->[3] = 30;
print Dumper($ref3), "\n";
my $ref4;
my $val4 = $ref4->[3];
print Dumper($ref4), "\n";
$VAR1 = {
'key' => 'value'
};
$VAR1 = {};
$VAR1 = [
undef,
undef,
undef,
30
];
$VAR1 = [];
- 실행결과에서 알 수 있듯이, 원소를 추가할 경우는 익명 해시 또는 익명 배열에 해당 원소까지 추가가 되고, 배열의 경우는 해당 원소보다 앞쪽 인덱스의 원소들은 undef으로 채워진다.
- 반면에 원소를 읽으려고 시도했을 경우는 익명 해시 또는 익명 배열이 생성되지만 원소가 추가되지는 않는다.
만일 값이 정의되지 않은 변수를, 서브루틴의 파라메터로 전달하고 그 서브루틴 안에서 해시 레퍼런스 또는 배열 레퍼런스처럼 사용하려고 하면 주의해야 한다.
sub copy_param {
my $ref = shift;
$ref->{orange} = 5;
}
my $ref1;
$ref1->{apple} = 3;
copy_param($ref1);
print "ref1:", Dumper($ref1), "\n";
my $ref2;
copy_param($ref2);
print "ref2:", Dumper($ref2), "\n";
ref1:$VAR1 = {
'apple' => 3,
'orange' => 5
};
ref2:$VAR1 = undef;
- 일단 익명 해시 레퍼런스를 담은 이후에 서브루틴에 인자로 넘어가면 서브루틴 내에서 원소를 추가하면 그 익명 해시에 원소가 추가된다.
- 이 경우 $ref1의 값을 복사해서 $ref에 담고, 이 때 $ref1과 $ref는 동일한 익명 해시를 가리키는 레퍼런스이다.
- 그러나 아직 undef 상태에서 서브루틴에 인자로 넘어갈 경우는 서브루틴이 종료되어도 여전히 undef 그대로이다.
- 정확히는 서브루틴 내부에서는 $ref 변수에 autovivification 이 일어나지만, 이것이 $ref2에 영향을 주지 못한다.
따라서, 원하는 대로 동작하게 하려면 서브루틴 내에서 파라메터를 복사하지 말고, 파라메터의 alias인 @_의 원소들을 직접 접근해야 한다.
sub alias_param {
$_[0]->{orange} = 5;
}
my $ref1;
$ref1->{apple} = 3;
alias_param($ref1);
print "ref1:", Dumper($ref1), "\n";
my $ref2;
alias_param($ref2);
print "ref2:", Dumper($ref2), "\n";
ref1:$VAR1 = {
'apple' => 3,
'orange' => 5
};
ref2:$VAR1 = {
'orange' => 5
};
- 두번째 경우도 제대로 autovivification 이 된 걸 알 수 있다.
2.3. 객체 메쏘드의 레퍼런스
일반적인 서브루틴에 대한 레퍼런스는 쉽게 만들고 사용할 수 있다:
my $ref = \&func;
$ref->('param');
그런데, /OOP의 객체의 메쏘드에 대한 레퍼런스는 어떻게 만들 수 있을까?
my $p1 = Person->new();
my $ref = \&$p1->func;
my $ref = \$p1->func;
$ref->('param');
위 두 가지는 다 실패한다. $p1->func
가 먼저 실행되고 그 리턴값을 코드 레퍼런스로 다루려고 하거나(첫번째), 리턴값의 레퍼런스를 받아서 그 레퍼런스를 사용하여 호출을 하려(두번째) 하기 때문이다.
구글링해보니 Perl cookbook 에 내용이 있었다:
위 두 곳의 내용과 주인장이 이래저래 테스트해 본 결과를 정리하면 다음과 같다.
Perl Cookbook에서 최선의 방안으로 제시하는 방법은 /Closure를 사용하는 것이다:
$mref = sub { $p1->func(@_) };
$mref->('param');
이 경우 $p1이 변수 스코프에서 벗어난 상태라도 여전히 호출 가능하다:
my $mref;
{
my $p1 = Person->new();
$mref = sub { $p1->func(@_) };
}
$mref->('param');
유의할 점. 만약 $mref가 설정된 후에 $p1이 다른 객체를 가리키도록 변경되었다면, $mref는 새로 바뀐 객체에 대해 메쏘드를 호출하게 된다.
그 외에 몇 가지 방법은 (위 링크들에 언급되었거나 내가 시도해보거나 등)
일반적인 레퍼런스처럼 가져와서 사용:
$mref = \&Person::func;
$mref->($p1,'param');
UNIVERSAL 클래스에서 제공하는 can
메쏘드(/OOP참조)를 써도 비슷하다:
$mref = $p1->can('func');
$mref->($p1,'param');
can 메쏘드를 이용하려면 반드시 객체가 있어야 하니까 패키지 이름을 사용한 위의 방법보다 안 좋아 보이긴 한다. 게다가 두 방법 다 어차피 객체 자체를 첫번째 인자로 줘야 하기 때문에, $p1->can(...)
와 같이 레퍼런스를 얻은 후에 호출은 $mref->($p2,...)
처럼 다른 객체에 대해 쓸 수도 있다.
만일 메쏘드 이름을 스트링이 아니라 동적으로 결정하려면:
my $name = 'func';
$mref = $p1->can($name);
$mref = \&{"Person::$name"};
위의 마지막 줄의 방법은, strict 프라그마, 특히 strict refs
가 적용된 상태에서도 쓸 수 있다. (strict 참고)
내가 테스트해본 바로는 슈퍼클래스의 메쏘드에 대해서도 다 잘 동작한다. 물론 패키지 이름을 적어준 방법의 경우는 해당 슈퍼클래스의 패키지 이름을 찾아서 적어줘야 하므로 사용하기 매우 까다로와지겠다.
그리고 위의 Perl Monks 링크에 가 보면 이외에 여러 가지 아이디어가 나와 있다. 나도 다 읽어보진 않았음.
2.4. 종종 저지르는 실수들
귀신에 홀린 듯 이럴 때가 생긴다:
1) 루프 안에서 새로운 레퍼런스 만들기
my @array;
while (...) {
push @AoA, \@array;
}
-
@array
선언이 루프 안에 들어가거나,
-
push @AoA, [ @array ];
를 해서 익명 배열 레퍼런스를 다시 생성해야 한다.
2) 레퍼런스와 반복연산자 x
use Data::Dumper;
my @arr1 = ( { a => 3 }, { a => 3 } );
print Dumper(\@arr1), "\n";
my @arr2 = ( ( { a => 3 } ) x 2 );
print Dumper(\@arr2), "\n";
두번째 경우는 하나의 익명 해시의 레퍼런스가 두 번 나오는 것이라 첫번째 경우와 다르다.
$VAR1 = [
{
'a' => 3
},
{
'a' => 3
}
];
$VAR1 = [
{
'a' => 3
},
$VAR1->[0]
];
3) autovivify되기 전에 복사하기
이것은 앞에서 함수 인자로 전달하는 것과 같은 상황
my %hash;
my $shortcut1 = $hash{deep}{deep}{deep}{level};
$shortcut1->{key1} = 10;
print Dumper(\%hash);
$hash{deep}{deep}{deep}{level}{key2} = 20;
my $shortcut2 = $hash{deep}{deep}{deep}{level};
$shortcut2->{key3} = 30;
print Dumper(\%hash);
-
$shortcut1
과 $shortcut2
는 동일하게 복잡한 %hash
의 내부를 간단히 접근할 수 있게 하려고 쓴 것이지만, $shortcut1은 무의미하다. 2)에서 키와 값을 추가한 것은 %hash에 아무런 영향을 주지 못한다.
VAR1 = {
'deep' => {
'deep' => {
'deep' => {}
}
}
};
$VAR1 = {
'deep' => {
'deep' => {
'deep' => {
'level' => {
'key3' => 30,
'key2' => 20
}
}
}
}
};
4. Comments
컴퓨터분류