[첫화면으로]Perl/Closure

마지막으로 [b]

Closure 에 관하여

1. 클로저 사용하기 Using Closure (번역)
1.1. 클로저란 무엇인가?
1.2. 써먹을 수 있는 간단한 클로저
1.3. 코드 레퍼런스를 사용한 클로저
2. perldoc Perlsub 중에서
3. perldoc Perlref 중에서
4. perlfaq 에서
5. Intermediate Perl 에서
6. 기타 & comments

1. 클로저 사용하기 Using Closure (번역)

클로저는 꽤 많은 용도가 있지만, 일반적으로 하나의 개념으로 요약할 수 있다: 렉시컬 범위의 변수(my()를 사용하여 생성한)가 그 변수의 범위(변수가 선언된 블록)가 닫힌 후에도 존재하는 것이다.

1.1. 클로저란 무엇인가?

다행히도, [Perl FAQ 7장]에서 이 질문에 대한 답을 하고 있다.

perldoc -q closure

Found in perlfaq7.pod 클로저는 무엇인가?

클로저에 대해서는 [perlref]에 문서화되어 있다. Closure는 정확하지만 설명하기는 힘든 뜻의 컴퓨터 과학 용어이다. Perl에서 클로저는 자신의 영역 바깥쪽에 있는 렉시컬 변수를 참조하고 있는 익명 서브루틴으로 구현된다. 이런 구문은 서브루틴이 정의되는 시점에 있던 변수를 신비롭게 참조하게 된다.

이 말은, 다음과 같이 할 수 있다는 뜻이다:
  {                       # $name은 이 블록 안에서만 존재한다
    my $name = "Jeff";    # $name은 렉시컬 변수이다
    sub whoami { $name }  # 렉시컬 변수 $name을 여기서 사용하면...
  }                       # $name은 영역을 벗어나도 존재한다

(맞다, 이런 건 바보같은 짓이다, 하지만 그래야 할 이유가 있다고 가정하자) 우리는 효과적으로 whoami() 함수(와 저 블록 안에 우리가 넣을 수 있는 다른 것들)에서 전용으로 접근할 수 있는(private) 변수를 만들어냈다. 우리가 함수 자체를 변경하지 않는 이상은 저 함수에서 반환되는 값을 변경할 수 없다. 이 예는 너무 간단하니 좀 더 동적인 예를 골라보자.

1.2. 써먹을 수 있는 간단한 클로저

우리가 어떤 카운터 함수를 만들고 싶다고 해보자. 호출할 때마다, 기존값에서 하나 증가된 숫자를 얻고 싶다. 그리고 우리가 두 개 이상의 카운터를 사용하고 싶다고 하자. 우리는 별개의 카운터 함수들을 만들 수도 있고, 인자를 받아 배열 안에 있는 특정한 카운터 값을 증가시키는 하나의 카운터 함수를 만들 수 있도 있다:
 {
    my @values;           # @values 는 counter()에서만 참조할 수 있다
    sub counter {
      my $which = shift;  # 증가시킬 원소
      ++$values[$which];  # 새로운 값을 반환
    }
  }

  for (1..5) { print counter(0) }  # 12345
  for (1..3) { print counter(1) }  # 123
  for (1..2) { print counter(0) }  # 67

배열 대신에 해쉬를 사용하여 다음과 같이 할 수도 있겠다:
  $yet_another_user = counter("Perl");
  $and_a_competitor = counter("Python");

우리가 만든 함수를 수정해서 증가치 값을 받아들이게 하고, 초기값을 설정하는 함수를 추가할 수 있다:
  {
    my %ctr;  # start()와 counter()에서 "공유한다"

    sub start {
      my ($which,$value) = @_;
      $ctr{$which} = $value || 0;
    }

    sub counter {
      my ($which,$inc) = @_;
      $ctr{$which} += $inc;
    }
  }

  start("foo",5);
  print counter("foo",10);  # 15
  print counter("foo",10);  # 25
  print counter("foo",10);  # 35

  # starts at 0 by default
  print counter("bar",-1);  # -1
  print counter("bar",-1);  # -2
  print counter("bar",-1);  # -3

그치만 아주 신기한 건 아니다 -- 유일하게 특별한 것은 %ctr 변수가 두 함수에서 공유되고, 변수가 정의된 블록이 끝난 다음에도 오랫동안 존재하는 것 정도이다.

우리가 함수를 호출하고, 그 함수 안에서 함수에 넘겨준 인자와 관련이 있는 또다른 함수를 정의할 수 있다면 흥미로울 것 같다:
  sub make_counter {
    my ($start,$inc) = @_;
    sub ctr { $start += $inc }
  }

그러나 이것은 몇 가지 이유 때문에 동작하지 않는다. 서브루틴의 선언은 말 그대로...선언인다. 선언은 어떤 값(서브루틴의 레퍼런스나 서브루틴 자체(애초에 불가능하지만, 여하튼)와 같은)을 반환하지 않는다. 또 ctr() 함수는 딱 한 번 정의되므로, 이 함수는 make_counter()를 첫번째로 호출할 때까지만 $start와 $inc값을 보관할 것이다(And the ctr() function is defined only once, so it will only keep the value of $start and $inc before and during the first call to make_counter()). 따라서 설령 당신이 ctr()의 레퍼런스를 반환하게 하여 이 문제를 해결하려고 해도:

  #!/usr/bin/perl -w

  sub make_counter {
    my ($start,$inc) = @_;
    sub ctr { $start += $inc }
    return \&ctr;
  }

  $one = make_counter(0,1);
  $ten = make_counter(0,10);

  print $one->(), $ten->();
여전히 기대한 결과를 얻지 못한다. 첫째로, -w 스위치를 사용하면 (안 할 이유가?), Variable "$start" will not stay shared at inner line 6Variable "$inc" will not stay shared at inner line 6라는 경고를 볼 것이다. 이건 컴파일 때 나오는 경고라는 것에 유의하라. Perl은 당신이 안 좋은 상태에 있음을 알려 줄 수 있다. 그 다음으로 Perl이 프린트하는 것을 보면 12 즉 숫자1 다음 숫자2가 나온다. 우리가 기대한 1 다음 10이 아니다. 이것은 ctr()함수가 재정의되지 않기 때문이며, (Perl이 이 부분도 경고를 할 수 있었다1) 또한 ctr()은 두 변수들의 첫번째 값을 사용하기 때문이다.

1.3. 코드 레퍼런스를 사용한 클로저

해결책은 익명 서브루틴, 즉 코드 레퍼런스를 사용하는 것이다:
  $coderef = sub {
    # ...
  };

sub 키워드 뒤에 이름이 없다 (그래서 익명 서브루틴이다). 또 이것은 표현식(expression)이지 선언(declaration)이 아니기 때문에 닫는 중괄호 뒤에 세미콜론이 있다. 이 데이타 타입을 사용하여, 우리가 원하는 클로저를 생성할 수 있다:

  #!/usr/bin/perl -w

  sub make_counter {
    my ($start,$inc) = @_;
    return sub { $start += $inc }
  }

  $one = make_counter(0,1);
  $ten = make_counter(0,10);

  print $one->(), $ten->();

우리가 기대한 대로 110(1, 다음 10)이 출력된다. 내 사악한 개인 사정 때문에, make_counter() 함수는 초기값만을 받고, 증가치는 반환되는 함수 레퍼런스에서 처리하기를 원한다고 해보자.

  sub make_counter {
    my $start = shift;
    return sub { $start += ($_[0] || 1) }
  }

  $ctr = make_counter(10);  # 카운터의 초기값은 10이다
  print $ctr->();           # 기본적으로는 1씩 증가한다
  print $ctr->(5);          # 5 증가하게 한다

위 코드는 11 다음에 16을 출력한다. 좀 더 나아가 보자. 인자 없이 호출하면 이전에 사용한 증가치를 다시 사용하도록 하면 어떨까? 또 비슷한 식으로, 증가치값을 make_counter() 함수에 전달하고, 이후에는 다시 전달하지 않아도 되게 할 수도 있다.

  sub make_counter {
    my $start = shift;
    my $inc = shift || 1;
    return sub { $start += (@_ ? ($inc = $_[0]) : $inc) }
  }

  $ctr = make_counter(10,5);  # 카운터의 초기값은 10이다
  print $ctr->();             # 증가치의 기본값은 5이다
  print $ctr->(1);            # 1 증가하게 한다

이 코드는 15 다음에 16을 출력한다. $inc와 $start변수 둘 다 반환된 코드 레퍼런스(지금부터는 이걸 클로저라 생각하자)에서만 접근할 수 있다(private). 이 코드에서 가장 복잡한 부분은 아마 서브루틴 레퍼런스를 반환하는 부분일 것이다. 서브루틴 레퍼런스에 인자가 들어올 경우, $inc에 그 인자값을 할당한다. 그 후 인자가 있건 없건 $inc의 값을 사용하여 $start의 값을 증가시킨다. 이 부분은 다르게 작성할 수도 있다:
  $start += (@_ and $inc = $_[0], $inc);
  $start += ($inc = $_[0] || $inc);       # 이 경우는 인자로 0 은 허용되지 않는다

2. perldoc Perlsub 중에서

3. perldoc Perlref 중에서

4. perlfaq 에서

5. Intermediate Perl 에서

6. 기타 & comments

이름:  
Homepage:
내용:
 


컴퓨터분류 Perl
각주:
1. 실제로 돌려보면 그런 경고는 안 나오는 듯. 5.10에서

마지막 편집일: 2012-2-11 12:25 am (변경사항 [d])
2261 hits | Permalink | 변경내역 보기 [h] | 페이지 소스 보기