[첫화면으로]Perl/레퍼런스

마지막으로 [b]

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.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 과 서브루틴 파라메터

아직 값이 정의되지 않은 변수를, 해시 또는 배열의 레퍼런스인 것처럼 원소의 값을 읽거나 쓰려고 시도하면 자동으로 그 변수에 익명 해시 또는 익명 레퍼런스를 생성하여 할당해준다. 이것을 autovivification이라고 한다.

use Data::Dumper;

    my $ref1;                   # undef
    $ref1->{key} = "value";     # 해시 레퍼런스처럼 사용하여 원소 추가
    print Dumper($ref1), "\n";

    my $ref2;                   # undef
    my $val2 = $ref2->{key};    # 해시 레퍼런스처럼 사용하여 원소 읽기 시도
    print Dumper($ref2), "\n";

    my $ref3;                   # undef
    $ref3->[3] = 30;            # 배열 레퍼런스처럼 사용하여 원소 추가
    print Dumper($ref3), "\n";

    my $ref4;                   # undef
    my $val4 = $ref4->[3];      # 배열 레퍼런스처럼 사용하여 원소 읽기 시도
    print Dumper($ref4), "\n";

$VAR1 = {
          'key' => 'value'
        };

$VAR1 = {};

$VAR1 = [
          undef,
          undef,
          undef,
          30
        ];

$VAR1 = [];




만일 값이 정의되지 않은 변수를, 서브루틴의 파라메터로 전달하고 그 서브루틴 안에서 해시 레퍼런스 또는 배열 레퍼런스처럼 사용하려고 하면 주의해야 한다.

sub copy_param {
    my $ref = shift;     # 인자값이 복사된다
    # 또는 아래처럼 해도 마찬가지
    # my ( $ref ) = @_;

    $ref->{orange} = 5;
}

    my $ref1;                               # undef
    $ref1->{apple} = 3;                     # autovivification 이 먼저 이뤄진 후
    copy_param($ref1);                      # 서브루틴에 인자로 넘겨주고
    print "ref1:", Dumper($ref1), "\n";     # 확인

    my $ref2;                               # undef
    copy_param($ref2);                      # 서브루틴에 넘겨주고
    print "ref2:", Dumper($ref2), "\n";     # 확인

ref1:$VAR1 = {
          'apple' => 3,
          'orange' => 5
        };

ref2:$VAR1 = undef;

따라서, 원하는 대로 동작하게 하려면 서브루틴 내에서 파라메터를 복사하지 말고, 파라메터의 alias인 @_의 원소들을 직접 접근해야 한다.
sub alias_param {
    $_[0]->{orange} = 5;    # $_[0]은 인자와 alias 관계
}

    my $ref1;                               # undef
    $ref1->{apple} = 3;                     # autovivification 이 이뤄진 후
    alias_param($ref1);                     # 서브루틴에 인자로 넘겨주고
    print "ref1:", Dumper($ref1), "\n";     # 확인

    my $ref2;                               # undef
    alias_param($ref2);                     # 서브루틴에 넘겨주고
    print "ref2:", Dumper($ref2), "\n";     # 확인

ref1:$VAR1 = {
          'apple' => 3,
          'orange' => 5
        };

ref2:$VAR1 = {
          'orange' => 5
        };

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);    # can()의 인자로 변수를 넣는 건 특별할 게 없다.
$mref = \&{"Person::$name"}; # 이건 좀 기괴해 보인다.
위의 마지막 줄의 방법은, strict 프라그마, 특히 strict refs가 적용된 상태에서도 쓸 수 있다. (Perldoc:strict 참고)

내가 테스트해본 바로는 슈퍼클래스의 메쏘드에 대해서도 다 잘 동작한다. 물론 패키지 이름을 적어준 방법의 경우는 해당 슈퍼클래스의 패키지 이름을 찾아서 적어줘야 하므로 사용하기 매우 까다로와지겠다.

그리고 위의 Perl Monks 링크에 가 보면 이외에 여러 가지 아이디어가 나와 있다. 나도 다 읽어보진 않았음.

2.4. 종종 저지르는 실수들

귀신에 홀린 듯 이럴 때가 생긴다:

1) 루프 안에서 새로운 레퍼런스 만들기

my @array;                  # 변수 선언이 루프 밖이다
while (...) {
    # 매 루프마다 @array에 뭔가를 채우고
    push @AoA, \@array;     # 그 레퍼런스를 배열의 배열에 차곡차곡 저장해봤자
}
# 이 시점에서 @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};  # 1)
$shortcut1->{key1} = 10;                         # 2)
print Dumper(\%hash);                            # 3)

$hash{deep}{deep}{deep}{level}{key2} = 20;       # 4)
my $shortcut2 = $hash{deep}{deep}{deep}{level};  # 5)
$shortcut2->{key3} = 30;                         # 6)
print Dumper(\%hash);                            # 7)
$shortcut1$shortcut2는 동일하게 복잡한 %hash의 내부를 간단히 접근할 수 있게 하려고 쓴 것이지만, $shortcut1은 무의미하다. 2)에서 키와 값을 추가한 것은 %hash에 아무런 영향을 주지 못한다.

VAR1 = {
          'deep' => {
                      'deep' => {
                                  'deep' => {}
                                }
                    }
        };
$VAR1 = {
          'deep' => {
                      'deep' => {
                                  'deep' => {
                                              'level' => {
                                                           'key3' => 30,
                                                           'key2' => 20
                                                         }
                                            }
                                }
                    }
        };

3. 기타

4. Comments

이름:  
Homepage:
내용:
 


컴퓨터분류

마지막 편집일: 2014-4-16 10:14 am (변경사항 [d])
2769 hits | Permalink | 변경내역 보기 [h] | 페이지 소스 보기