October 2008 Archives

컴퓨터로 작업을 하다 보면 여러 대의 컴퓨터에 동시에 로그인해서 같은 작업을 해야 하는 경우가 많다.

UNIX/Linux의 X-windows상이면 예전에도 소개한 적이 있는 google에서도 쓴다는 clusterssh 같은 것을 이용할 수 있으나 현실적으로 회사에서 업무용으로 Windows 운영체제를 많이 사용하므로 X터미널이나 VMware를 통해 리눅스를 사용하거나 하지 않으면 쓰기 어렵다. 그래서 Windows용으로는 비슷한 것이 없는지 한 번 찾아보았는데...

PuTTYCS라는 아주 작고 멋진 프로그램을 발견했다. (google에서 한글페이지를 검색해보니 소개한 곳이 하나도 없는 것으로 봐서 한글 싸이트로는 제일 먼저 소개하는 것이라 생각됨) 이것은 공개 SSH클라이언트인 putty를 여러 개 띄우고 PuTTYCS에서 입력을 하면 모든 창에 같은 입력이 전달되어 동시에 같은 작업을 할 수 있게 해주는 프로그램으로 창들을 cascade,tile등의 형태로 정렬도 가능하고 filter를 통하여 특정 그룹의 창에만 작동하도록도 할 수 있다.

홈페이지: http://www.millardsoftware.com/  (왼쪽에서 PuTTYCS 메뉴를 선택하고 다운로드 하면 된다.)

사용법은 스크린샷을 올리고 구구절절 침을 튀겨가며 말하지 않아도 기본적 IT 소양을 갖춘 사람이라면 금방 사용할 수 있을 거라 생각됨~
요즘 그냥 재미로 Catalyst 라는 Perl로 만든 웹프레임웍을 들여다보고 있는데( 이걸 사용해서 뭘 만들겠다는 건 아니고 그냥 어떤 식으로 구현되어 있나, 내부적인 구조에 어떤 테크닉을 사용하고 있느냐가 궁금해서~ ) 코드 체계를 분석하려니 Catalyst에 사용되는 각종 객체들의 상속관계부터 파악해야 뭔가 기본적인 구조가 잡힐 것 같아서 혹시 이러한 클래스간의 상속관계를 그래프나 다이아그램으로 그려주는 모듈은 없는지 CPAN을 뒤져 보았다.

그 결과 찾은 모듈이 UML::Class::Simple 모듈!!!

UML::Class::Simple 모듈을 설치한 후 Catalyst Tutorial에 있는 MyApp 라는 예제 뼈대 웹 어플리케이션을 생성하고 UML::Class::Simple 모듈을 설치하면 생기는 명령어인  umlclass.pl 이란 명령으로 다음과 같이 명령을 내렸다.


>umlclass.pl -M Catalyst -M MyApp -I ./MyApp/lib/ -I /usr/share/perl5 -p "^(Catalyst|MyApp)" -o catalyst.png


명령옵션에 대한 자세한 설명은 http://search.cpan.org/dist/UML-Class-Simple/ 의 문서를 참고

그 결과는 ?

<크게 보려면 그림을 클릭>



알흠답지 아니한가?
http://kldp.org/node/98881#comment-462014
http://web.suapapa.net:8080/wordpress/?p=391
에서 사랑의 이름궁합을 Python으로 만들 걸 보고 재미있겠다 싶어 Perl로 한 번 만들어 봤음~

suapapa님은 ㅈ를 2획 ㅊ을 3획으로 했던데 http://www.happyname.co.kr/name/index_name_08.htm 을 보면 ㅈ는 3획 ㅊ는 4획 이 맞는 것 같아서 고침.

온라인상에서 해보기 -> http://aero.sarang.net/love

참고:
Lingua::KO::Hangul::Util 모듈

http://www.kfunigraz.ac.at/~katzer/korean_hangul_unicode.html


<LoveMatching.pm 모듈>

package LoveMatching;

use strict;
use warnings;
use utf8;
use Lingua::KO::Hangul::Util qw/:all/;
use base qw/Exporter/;

use version; our $VERSION = qv("0.0.1");

our @EXPORT_OK = qw/match_by_name/;

sub match_by_name {
    my ($name1,$name2) = @_;
    my @name1chars = split //, $name1;
    my @name2chars = split //, $name2;
    my @namepool;
    while (@name1chars) {
        push @namepool, shift @name1chars;
        push @namepool, shift @name2chars;
    }
    binmode STDOUT, ':encoding(UTF-8)';
    print "@namepool\n";
    my @cntpool = map { get_stroke_count($_) } @namepool;
    print love_sum( @cntpool ), " %\n";
}

sub love_sum {
    my @cnts = @_;
    print "[", join(' ,', @cnts),"]\n";
    if ( (join '',@cnts) > 100 ) {
        my @newcnts;
foreach ( 0 .. $#cnts-1 ) {
push @newcnts, ($cnts[$_]+$cnts[$_+1]) % 10;
}
        return love_sum(@newcnts);
    }
    else {
        return (join '', @cnts)+0;
    }
}

sub get_stroke_count {
    my ($chr) = @_;
    my %stroke_cnt = (
        #초성
        "\x{1100}" => 1, "\x{1102}" => 1, "\x{1103}" => 2, "\x{1105}" => 3,  # ㄱㄴㄷㄹ
        "\x{1106}" => 3, "\x{1107}" => 4, "\x{1109}" => 2, "\x{110B}" => 1,  # ㅁㅂㅅㅇ
        "\x{110C}" => 3, "\x{110E}" => 4, "\x{110F}" => 2, "\x{1110}" => 3,  # ㅈㅊㅋㅌ
        "\x{1111}" => 4, "\x{1112}" => 3,                                    # ㅍㅎ
        #중성
        "\x{1161}" => 2, "\x{1163}" => 3, "\x{1165}" => 2, "\x{1167}" => 3,  # ㅏㅑㅓㅕ
        "\x{1169}" => 2, "\x{116D}" => 3, "\x{116E}" => 2, "\x{1172}" => 3,  # ㅗㅛㅜㅠ
        "\x{1173}" => 1, "\x{1175}" => 1,                                    # ㅡㅣ
        #종성
        "\x{11A8}" => 1, "\x{11AB}" => 1, "\x{11AE}" => 2, "\x{11AF}" => 3,  # ㄱㄴㄷㄹ
        "\x{11B7}" => 3, "\x{11B8}" => 4, "\x{11BA}" => 2, "\x{11BC}" => 1,  # ㅁㅂㅅㅇ
        "\x{11BD}" => 3, "\x{11BE}" => 4, "\x{11BF}" => 2, "\x{11C0}" => 3,  # ㅈㅊㅋㅌ
        "\x{11C1}" => 4, "\x{11C2}" => 3,                                    # ㅍㅎ
    );

    my @jamos = map { split //, decomposeJamo($_) } split //, decomposeSyllable($chr);
    my $strokes = 0;
    foreach (@jamos) { $strokes += $stroke_cnt{$_} }
    return $strokes;
}

1;


<LoveMatching_test.pl 테스트코드>

#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use LoveMatching qw/match_by_name/;
match_by_name("이명박","강만수");

<결과>
>perl LoveMatching_test.pl
이 강 명 만 박 수
[2 ,4 ,7 ,6 ,7 ,4]
[6 ,1 ,3 ,3 ,1]
[7 ,4 ,6 ,4]
[1 ,0 ,0]
100 %

<명령행에서 실행 예>
>perl -MLoveMatching=match_by_name -Mutf8 -e 'match_by_name("연정훈","한가인")'
연 한 정 가 훈 인
[5 ,6 ,6 ,3 ,6 ,3]
[1 ,2 ,9 ,9 ,9]
[3 ,1 ,8 ,8]
[4 ,9 ,6]
[3 ,5]
35 %

Perl의 Attribute (2)

| | Comments (2) | TrackBacks (0)
앞선 포스팅 을 통해서 Perl의 attribute를 소개했는데 attributes 모듈을 통해서 어떤 작업을 하려면 무척이나 복잡하고 귀찮을 경우가 많다. 따라서 이러한 작업을 쉽게 해주는 모듈이 나왔는데 그것이 Attribute::Handlers 라는 모듈이다.

attributes모듈과 Attribute::Handlers 라는 모듈은 모두 Perl이 배포될 때 기본적으로 포함되는 CORE모듈이므로 별도로 모듈을 설치할 필요가 없다.

Attribute::Handlers 모듈은 내부적으로 attribute를 사용할 때 필요한 MODIFY_[TYPE]_ATTRIBUTES 같은 서브루틴 생성과 심볼테이블 검색 및 캐싱을 자동적으로 해주기 때문에 이전의 예제에서 봤던 복잡한 작업의 수고를 덜어준다.

그러면 Attribute::Handlers 모듈의 기본적인 골격을 통해 어떻게 동작하는지 보도록 하자.

<예제코드>

#!/usr/bin/perl
use strict;
use warnings;
use Attribute::Handlers;

# MyAttr attribute가 오면 호출되는 서브루틴을 다음과 같이 정의
sub MyAttr : ATTR {
    my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
    print "@_\n";
    print "\$package : $package\n";
    print "\$symbol name: ",*{$symbol}{NAME},"\n";
    print "\$referent :",$referent,"\n";
    print "\$attr : $attr\n";
    print "\$data (dereference): @$data\n";
    print "\$phase: $phase\n";
    print "-------------------------------\n";
}

sub test : MyAttr(qw/arg1 arg2/) {
    print "test\n";
}

<결과>
main GLOB(0x680a30) CODE(0x604f90) MyAttr ARRAY(0x65e050) CHECK
$package : main
$symbol name: test
$referent :CODE(0x604f90)
$attr : MyAttr
$data (dereference): arg1 arg2
$phase: CHECK

결과를 보면 알겠지만 attribute를 정의할 때 넘어가는 인자는 차례로 attribute가 정의된 패키지 $package , attribute가 정의된 대상 typeglob의 레퍼런스(여기서는 \*test) $symbol, attribute가 정의된 것의 레퍼런스(여기서는 test 서브루틴의 레퍼런스) $referent , attribute의 이름 $attr , attribute에 부가적으로 정의해준 데이터들의 레퍼런스 $data, Perl의 실행 시 어떤 단계에서 적용할 것인가를 말하는 $phase(지정하지 않으면 기본으로 CHECK)  가 넘어감을 알 수 있다.

어떤 attribute를 처리하는 sub MyAttr : ATTR  식의 서브루틴을 정의할 때 어떠한 변수타입이나 서브루틴에만 적용되도록 다음처럼 명시적으로 지정할 수 있으며

sub MyAttrSub : ATTR(CODE)     # 서브루틴(코드)에만 적용되는 MyAttrSub attribute를 정의
sub MyAttrScalar : ATTR(SCALAR) #  스칼라에만 적용되는 MyAttrScalar attribute를 정의

다음과 같이 Perl 실행 시 어떤 단계(phase)에서 적용될지를 추가적으로 명시적으로 지정할 수도 있다.

sub MyAttrSub : ATTR(CODE,CHECK)
sub MyAttrScalar : ATTR(SCALAR,BEGIN)
sub SomeAttr : ATTR(HASH,BEGIN,END)    #혹은 여러단계에 적용하도록도 가능

* BEGIN,CHECK등 Perl이 실행될 때 적용되는 단계(phase)는 http://perldoc.perl.org/perlmod.html#BEGIN%2c-UNITCHECK%2c-CHECK%2c-INIT-and-END 을 참고

만약에 다음과 같이 :으로 시작하는 attribute가 여러 개 나온다면

sub setup : Chained('/') : PathPart('foo') : Args(1) {..}

:이 등장할때 마다 Chained, PathPart, Args attribute에 대해 각각 호출이 일어나게 된다.

Attribute::Handlers 모듈은 이 이외에도 더 많은 기능을 지원하는 데 자세한 내용은 Attribute::Handlers 모듈 문서를 참고하도록 하자.

그러면 이제 응용해서 어떤 서브루틴에 _log 라는 attribute를 정의하면 서브루틴의 실행전과 후에 로그를 찍는 프로그램을 만들어 보자.

<예제 프로그램>

#!/usr/bin/perl
use strict;
use warnings;

use constant DEBUG => 1;

use Attribute::Handlers;

sub _log : ATTR(CODE) {
    my ($pkg, $sym, $code) = @_; # 여기서 필요한 건 앞의 3개의 인자 뿐

    if( DEBUG ) {
        my $name = *{ $sym }{NAME};

        no warnings 'redefine';

        *{ $sym } = sub {
            print "Entering sub $pkg\:\:$name\n";
            my @ret = $code->( @_ );   # $code 코드(서브루틴) 레퍼런스를 통해 원래 서브루틴 실행
            print "Leaving sub $pkg\:\:$name\n";
            return @ret;
        };
    }
}

sub do_something : _log {
    print "I'm doing something.\n";
}

do_something();

<결과>

Entering sub main::do_something
I'm doing something.
Leaving sub main::do_something

결과를 보면 do_something 서브루틴의 호출 전과 후에 원하는 대로 로그 메시지가 찍혔음을 볼 수 있다.

그럼 마지막으로 이전에 복잡하게 구현했던 어떤 서브루틴의 모든 출력을 소문자로 만들어주는 Quiet attribute의 반대가 되는 모든 출력을 대문자로 만들어주는 Loud 라는 attribute를 Attribute::Handlers 모듈을 통해서 구현해 보자.

<Loud.pm>

package Loud;
use strict;
use warnings;
use IO::Capture::Stdout;
use Attribute::Handlers;

sub Loud :ATTR(CODE) {
    my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
    no strict 'refs';
    no warnings 'redefine';

    # redefines subroutines given the "Loud" attribute
    *{$package.'::'.*{$symbol}{NAME}} = sub {

        my $capture = IO::Capture::Stdout->new();
        $capture->start();

        # call the original subroutine
        my @rets = $referent->(@_);

        $capture->stop();
        foreach ( $capture->read ) { print "\U$_" }
        return @rets;
    }
}

1;

<Loud_test.pl>

#!/usr/bin/perl
use strict;
use warnings;
use base qw/Loud/;

# We want this function to be loud
sub foo : Loud {
  print "Purple is a nice color.\n";
  print "Hello, World !\n";
}

foo();

<결과>

PURPLE IS A NICE COLOR.
HELLO, WORLD !

Loud.pm 파일을 보면 알겠지만 Quiet.pm을 만들때 수동으로 해주었던 많은 dirty한 작업들 없이 원하는 바만 깔끔하게 구현 할 수 있음을 볼 수 있다.

사실 attribute라는 건 사용자 입장에는 자기가 이해하고 새로 만들 것이 아니면 원 개발자가 정의해준대로 그냥 사용해도 상관없다. 하지만 그 내부동작을 이해하고자 노력하는 과정에서 배우고 얻는게 많으므로 충분히 분석해볼 만한 가치가 있다고 본다.

추가로 Catalyst 같은 Perl 기반의 유명 웹 프레임웍에서는 attribute가 어떠한 과정을 거쳐서 어떻게 쓰이는지 궁금하다면 일본의 Yappo씨가 블로그에 올린 포스팅을 참고하면 된다.
네이버 인조이 재팬을 통한 번역: Catalyst의 Attributes가 초기화될 때 까지

Perl의 Attribute (1)

| | Comments (0) | TrackBacks (0)
요즘 많이 쓰이는 Perl 기반 웹프레임웍인 Catalyst 같은 코드들을 보면

sub some_sub : Private {
    ....
}

같은 형식으로 attribute라는 것(여기서는 : Private )을 쓰는 걸 종종 볼 수 있다.

이것은 Perl을 제법 공부했다는 사람에게도 생소한 경우가 많으며, 예제나 튜토리얼을 따라서 그대로 쓰고 있기는 하지만 그 내부 동작을 제대로 이해하지 못하고 기계적으로 쓰는 경우도 많다. 그 이유는 attribute의 동작을 이해하기 위해서는 Perl을 배우는 데 있어 고급주제에 속하는 모듈과 심볼테이블의 동작방식을 속속들이 알아야 하기 때문이다.

Perl에서는 perlsub 문서를 보면 알겠지만 lvalue 같은 표준적으로 제공하는 attribute가 이미 존재한다.

    my $val;
    sub canmod : lvalue {
    # return $val; this doesn't work, don't say "return"
    $val;
    }
    sub nomod {
    $val;
    }

이렇게 lvalue attribute를 정의한 서브루틴은 그 자체로 lvalue로 사용하여 값을 할당할 수 있다.

    canmod() = 5;   # assigns to $val
    nomod()  = 5;   # ERROR

Perl 5.6 부터는 여기에 사용자가 나름대로의 attribute를 정의할 수 있도록 확장되었다.(문서상에는 아직 실험적인 기능이라고 하지만 이미 많은 모듈과 프레임웍에서 널리 사용되고 있어 거의 정착된 기능으로 봐도 무방할 것 같다.)

그럼 처음으로 돌아가서 attribute는 무엇일까? attribute는 어떤 변수나 서브루틴에 별도의 의미를 부여하는 것이라고 보면 된다. 예를 들어 서브루틴이 실행될 때 서브루틴이 시작하고 끝나는 것을 추적하는 어떤 로그를 남기고 싶다고 하면 모든 서브루틴내에서 그 루틴을 포함하는 것이 아니라 다음처럼 기존의 서브루틴들에 _log라는 attribute만 붙여주면 서브루틴이 실행될 때 _log attribute 속성을 가지고 있으면 자동으로 로그를 찍도록 하겠다는 것이다.

sub mysub : _log {
    ....
}
sub yoursub : _log {
    ....
}

그러면 일단 나름대로의 attribute를 정의하고 코드를 실행해보자.

sub mysub : Loud {
    print "Hello, World!\n";
}

그러면 다음과 같은 에러가 난다.

Invalid CODE attribute: Loud at -e line 1
BEGIN failed--compilation aborted at -e line 1.

이것은 서브루틴에 정의한 Loud attribute가 유효하지 않다는 것이다. (서브루틴에 attribute를 붙였기 때문에 CODE attribute 라는 에러가 났다. )

그러면 : Loud 같은 attribute가 붙었을 때 Perl 내부적으로 어떻게 동작하는지 살펴보자.
Perl은 attribute 형식의 문법이 나오면 내부적으로 다음과 같은 코드로 실행된다.

sub mysub : Loud { ... 형식으로 나왔다면 다음과 같은 코드가 내부적으로 실행된다.

use attributes ( 'main', \&mysub, 'Loud');

뒤에 따르는 LIST는 차례대로 호출한 측의 패키지 이름, attribute가 사용된 것의 레퍼런스(여기서는 mysub 서브루틴의 레퍼런스), attribute의 목록(여러 개면 여러 개가 차례로)이 된다.

use는 모듈을 로딩하여 사용할 때 쓰는 명령어임은 잘 알고 있을 것이다. 하지만 use가 내부적으로 어떻게 동작하는지는 잘 모르는 사람이 많다. 다시 한 번 정리하고 넘어가자면

use SomeModule LIST; 는 (예: use List::Util qw/shuffle sum/;  이것은 use List::Util ('shuffle','sum'); 과 같은 의미)

BEGIN {
require SomeModule;
SomeModule->import LIST;
}

와 같은 의미이다.

그러면 use attributes ( 'main', \&mysub, 'Loud'); 는 어떻게 동작할까? 그렇다

BEGIN {
require attributes;
attributes->import('main',\&mysub,'Loud');
}

처럼 동작한다. 여기서 import는 모듈에서 모듈의 심볼과 호출 측 네임스페이스 내의 심볼을 연결시키는 심볼 export작업에 주로 사용되는 기본 서브루틴(따로 정의하지 않으면 비어 있음)으로 이것을 나름대로 정의 해서 다양한 용도로 쓸 수 있다. Perl의 고급 테크닉의 시작은 이것을 어떻게 잘 이해하고 이용하느냐가 첫 걸음이 된다.

그러면 이제 그다음 동작은 어떻게 진행되는지 보려면 어디로 추적해 들어가야 할까?
그렇다. attributes 모듈의 import 서브루틴이 어떻게 만들어져 있는지 보면 된다.

<attributes 모듈의 import 서브루틴>

sub import {
    @_ > 2 && ref $_[2] or do {
    require Exporter;
    goto &Exporter::import;
    };
    my (undef,$home_stash,$svref,@attrs) = @_;

    my $svtype = uc reftype($svref);
    my $pkgmeth;
    $pkgmeth = UNIVERSAL::can($home_stash, "MODIFY_${svtype}_ATTRIBUTES")
    if defined $home_stash && $home_stash ne '';
    my @badattrs;
    if ($pkgmeth) {
    my @pkgattrs = _modify_attrs($svref, @attrs);
    @badattrs = $pkgmeth->($home_stash, $svref, @pkgattrs);
    ...
    ...


이 소스를 보고
attributes->import('main',\&mysub,'Loud'); 가 호출 되었을 때 어떻게 되는지 분석해보자
->연산자를 통해서 모듈 내의 서브루틴을 호출하면 -> 왼쪽의 것이 첫 번째 인자로 넘어간다. 따라서
import('attributes','main',\&mysub,'Loud'); 로 호출이 된다.
그러면 $home_stash는 'main',  $svref은 \&mysub, @attrs은 'Loud' 가 될 것이다.
그다음 $svtype은 $svref의 레퍼런스 타입이 되므로 'CODE' 가 된다.
그다음 UNIVERSAL::can('main',"MODIFY_CODE_ATTRIBUTES") 가 실행되어 MODIFY_CODE_ATTRIBUTES라는 서브루틴이 실행가능한지를 채크하고 그 결과를 $pkgmeth 변수(성공이면 MODIFY_CODE_ATTRIBUTES 서브루틴에 대한 레퍼런스) 에 넣고 attribute들의 적합성을 채크한 다음 해당 $pkgmeth->('main',\&mysub, ('Loud')) 으로 해당 서브루틴을 호출한다.

그러면 attribute를 원하는대로 처리해주려면 attribute를 정의해준 변수나 서브루틴등 타입에 따라 MODIFY_[TYPE]_ATTRIBUTES( 서브루틴이면 MODIFY_CODE_ATTRIBUTES, 스칼라면 MODIFY_SCALAR_ATTRIBUTES 등) 라는 서브루틴을 정의해주면 될 것이라는 걸 눈치 챌 수 있을 것이다.

<테스트코드>
#!/usr/bin/perl
use strict;
use warnings;

sub MODIFY_CODE_ATTRIBUTES {
    print "MODIFY_CODE_ATTRIBUTES\n";
    print "@_\n";
    return;
}

sub mysub : Loud {
    print "Hello, World!\n";
}

<결과>

MODIFY_CODE_ATTRIBUTES
main CODE(0x663d20) Loud

여기서 넘어온 인자(@_)가 $pkgmeth->('main',\&mysub, ('Loud'))  호출에서 넘겨준 것과 같음을 알 수 있다.

attributes 모듈은 특정한 타입에 대해서 attribute들을 저장하고 가져오기 위한 FETCH_[TYPE]_ATTRIBUTES 서브루틴도 지원한다. 이것은 attributes 모듈로 넘어가는 인자가 변수나 서브루틴(코드)의 레퍼런스이기 때문에(레퍼런스는 메모리상에 위치한 주소로 unique한 값이다.) 필요하다면 그 레퍼런스를 해시의 키등으로 사용하여 어떤 attribute를 저장해놨다가 나중에 다시 꺼내어 볼 수 있는 것이다.

이 모든 것을 하나의 샘플코드로 정리해 보면 다음과 같다.

<샘플코드>

#!/usr/bin/perl
use strict;
use warnings;

my %attrs;

sub MODIFY_SCALAR_ATTRIBUTES {
    print "MODIFY_SCALAR_ATTRIBUTES\n";
    print "@_\n";
    my($package,$scalar_ref,@attrs)=@_;
    $attrs{$scalar_ref}=\@attrs; # %attrs 해시에 넘어온 attributes를 저장
    return;
    #return 'attr4'; # 지원하지 않는 속성을 return을 통해서 넘겨주면 에러가 발생되므로 예외 처리에 사용가능
}

sub FETCH_SCALAR_ATTRIBUTES {
    print "FETCH_SCALAR_ATTRIBUTES\n";
    print "@_\n";
    my($package,$scalar_ref)=@_;
    return $attrs{$scalar_ref};  # 저장했던 attributes를 넘겨준다.
}

sub MODIFY_CODE_ATTRIBUTES {
    print "MODIFY_CODE_ATTRIBUTES\n";
    print "@_\n";
    my($package,$code_ref,@attrs)=@_;
    $attrs{$code_ref}=\@attrs; # %attrs 해시에 넘어온 atttributes를 저장
    return;
}

sub FETCH_CODE_ATTRIBUTES {
    print "FETCH_CODE_ATTRIBUTES\n";
    print "@_\n";
    my($package,$code_ref)=@_;
    return $attrs{$code_ref};    # 저장했던 attributes를 넘겨준다.
}

my $var : attr1 attr2 attr3;
# 위처럼 attribute를 사용하면 실제로는 아래 처럼 동작한다.
#use attributes ('main', \$var, 'attr1', 'attr2', 'attr3');
# attributes 모듈의 import가 두번째 인자의 TYPE에 따라 FETCH_[TYPE]_ATTRIBUTES가
# 정의되어 있으면 호출한다.
print "@{ attributes::get(\$var) }\n";
# FETCH_SCALAR_ATTRIBUTES 가 호출된다. 인자는 패키지이름과 레퍼런스 2개


sub test : attr1 attr2 attr3 {
    print "test\n";
}
#use attributes ('main', \&test, 'attr1', 'attr2', 'attr3');
#위와 마찬가지로 뒤의 리스트들은 attributes 모듈의 import의 인자로 들어간다.
print "@{ attributes::get(\&test) }\n";
# FETCH_CODE_ATTRIBUTES 가 호출된다

<결과>

MODIFY_CODE_ATTRIBUTES
main CODE(0x65e0c0) attr1 attr2 attr3
MODIFY_SCALAR_ATTRIBUTES
main SCALAR(0x604fa0) attr1 attr2 attr3
FETCH_SCALAR_ATTRIBUTES
main SCALAR(0x604fa0)
attr1 attr2 attr3
FETCH_CODE_ATTRIBUTES
main CODE(0x65e0c0)
attr1 attr2 attr3

위 예제는 attribute의 동작방식을 알아보기 위한 것이지 실제 어떻게 사용하는지의 예제로는 적합하지 않다.
다음은 어떤 서브루틴에 Quiet라는 attribute를 주면 해당 서브루틴이 표준출력으로 출력하는 모든 글자를 소문자로 만드는 예제이다.

<Quiet.pm>

package Quiet;
use strict;
use warnings;
use IO::Capture::Stdout;

my @cache;

CHECK {
    # attributes 모듈은 BEGIN 단계에서 로딩되므로 그 시점에서는 서브루틴이 완전히 로딩되지 않아서
    # 서브루틴의 코드에 대한 조작을 할 수 없다. 따라서 MODIFY_CODE_ATTRIBUTES 에서 cache에 던져놓고
    #  서브루틴이 로딩이 끝난 CHECK 단계에서 조작하기 위해 CHECK 서브루틴에 그 작업을 정의한다.
    no strict 'refs';
    my %code_cache;
    foreach (@cache) {
        my ($pkg, $ref, @attrs) = @$_;
        unless ($code_cache{$pkg}) {
            $code_cache{$pkg} = {};
            foreach my $sym ( values %{$pkg.'::'} ) {
                next unless *{$sym}{CODE};
                $code_cache{$pkg}->{*{$sym}{CODE}} = *{$sym}{NAME};
            }
        }
        my $sym = $pkg . '::' . $code_cache{$pkg}->{$ref};
        no warnings 'redefine';
        *{$sym} = sub {
            my $capture = IO::Capture::Stdout->new();
            $capture->start();

            # call the original subroutine
            my @rets = $ref->(@_);

            $capture->stop();
            foreach ( $capture->read ) { print "\L$_" }
            return @rets;
        };
    }
}


sub MODIFY_CODE_ATTRIBUTES {
    my ($package,$code_ref,@attrs) = @_;
    push @cache, [$package, $code_ref, @attrs];
    return grep { $_ !~ /^Quiet$/ } @attrs;  # Quiet attribute가 아니면 에러발생
}

1;


<Quiet_test.pl>

#!/usr/bin/perl
use strict;
use warnings;
use base qw/Quiet/;

# We want this function to be loud
sub foo : Quiet {
    print "Purple is a nice color.\n";
    print "Hello, World !\n";
}

foo();

<결과>

purple is a nice color.
hello, world !

이  코드가 이해된다면 고수들이 만든 예전에는 암호 같아만 보였던 모듈 및 코드들이 이해되기 시작하면서 Perl을 공부하는 데 있어 새로운 재미를 느낄 수 있는 길이 열릴 것이라고 본다.

하지만 뭐가 이리 복잡해? 라고 생각하는 사람도 있을 텐데 다행히 이런 attribute를 다루는 dirty한 작업을 손쉽게 해줄 수 있는 모듈들이 존재한다. 다음번에는 그러한 모듈인 Attribute::Handlers 라는 모듈을 소개해보도록 하겠다.

참고:
http://search.cpan.org/perldoc?attributes
http://www.myemy.com/article/class-component/

About this Archive

This page is an archive of entries from October 2008 listed from newest to oldest.

September 2008 is the previous archive.

November 2008 is the next archive.

Find recent content on the main index or look in the archives to find all content.

Creative Commons License
This weblog is licensed under a Creative Commons License.
Powered by Movable Type 4.21-en