2014/12/17

Perl과 Ruby, Python의 함수 호출과 함수 참조에 대한 차이

루비와 파이썬에서 함수 호출과 함수 참조에 대한 차이
라는 글을 보고 Perl에서는 어떤지 한번 정리해 봅니다. 우선 윗글에 있는 코드를 Perl식으로 바꾸면 아래와 같습니다.

#!/usr/bin/env perl

sub a {
    print 'a';
}

sub b {
    print 'b';
}

sub f {
    my ($arg1, $arg2) = @_;
    $arg1;
}

f(a,b);


Perl에서는 Ruby,Python과 달리 변수에는 sigil이라고 하는 $를 앞에 붙여서 변수를 만듭니다. 따라서 변수명과 함수명이 같아도 엄연히 다른것이며 충돌하거나 암묵적으로 호출될 일은 없습니다.
위의 f함수에서처럼 인자로 그냥 bareword를(여기서는 a b) 사용했을경우 선언된 함수이름이 존재하면 함수를 호출하게 됩니다. 따라서 f(a,b)는 a함수, b함수 가 호출된 결과가 들어가게 됩니다. 각 함수가 차례로 호출되면서 ab가 차례로 찍히고 Perl은 { }블록형태의 함수에서 return문으로 명시적으로 리턴하지 않으면 블록내에서 마지막으로 평가된 값이 리턴됩니다. 여기서는 print 문이 성공하면 참(1)값을 돌려주기 때문에 각 함수의 마지막 평가값은 1이 되어서 결과적으로는 f(1,1)이 호출되고 f함수에서 마지막 평가값인 $arg1의 값은 1이 되어 f함수의 리턴값은 1이 됩니다. 마츠가 루비를 만들때 Perl을 많이 참고로 해서 그런지는 모르겠지만 여기 까지는 Ruby와 비슷하게 동작하는 것 같네요.

그럼 Python과 같이 함수의 참조가 넘어가서 f함수 내부에서 넘겨받은 함수 참조를 통해 호출하려면 Perl에서는 어떻게 해야 할까요?

#!/usr/bin/env perl

my $a = sub { print 'a'; };

sub b {
   print 'b';
}

my $b = \&main::b;

sub f {
    my ($arg1, $arg2) = @_;
    $arg1->();
    &$arg2;
}

f($a,$b);


Perl에서 함수레퍼런스는 함수가 호출되는 메모리상의 주소값을 가진 스칼라 변수 입니다. $같은 sigil이 없는 Python이나 Ruby에서는 동일한 이름을 가진 변수와 함수의 식별자(identifier)가 때로는 값 혹은 호출로 때로는 값 혹은 참조로 암묵적으로 동작하게 되지만 Perl에서는 확실히 구별됩니다. 위 Perl코드의 $a는 익명함수의 주소값을 $a라는 변수에 넣은것이고 $b는 이미 Perl의 기본 네임스페이스인 main패키지에 선언되어 심볼테이블에 기록된 b함수의 주소값을 $b변수에 넣은것입니다. 이제 두가지 방법으로 스칼라변수 $a, $b에 저장된 함수레퍼런스를 f함수에 넣어 호출하게 되면 f함수 내부에서 넘겨받은 함수 레퍼런스를 통해 함수를 호출 할때는 $arg1->() 처럼 뒤에 ->()를 붙여서 호출할 수도 있고 &$arg2 처럼 앞에 &를 붙여서도 호출 가능합니다.  Perl이 레퍼런스를 다루고 호출하는 문법을 보면 C나 Javascript 와 유사한 느낌적인 느낌이 들지 않나요?

Perl의 sigil이 코드를 noisy하게 보인다고 까는 사람도 있지만 저는 코드의 앞뒤를 뒤져보지 않아도 바로 이 코드의 의도를 명확하게 구별하고 알려주는 효자 같은 존재라고 봅니다.


PS: Perl 문법이 더 궁금한 분은 https://github.com/aero/perl_docs 을 참고하세요~

2014/08/15

Windows에서 특정 디렉토리 이하 모든 파일 및 디렉토리 지우기

Linux/Unix 계열에서 특정 디렉토리아래의 모든 디렉토리 파일을 지우려면 다음과 같이 하면 된다.

현재 디렉토리 아래의 some_directory 밑의 모든 파일 및 디렉토리 지움

rm -rf ./some_directory/*

이런 작업을 윈도우에서 하려면 어떻게 하면 될까?

del 명령과 rmdir 명령을 아무리 조합해도 한번에 만족할 만한 결과를 얻기 힘들다.
파일은 다 지워지는데 빈 디렉토리는 남아있다던가 특정 디렉토리 아래의 모든 것을 지우고 싶은데 그 특정 디렉토리도 지우지 않고는 안되는... 그래서 다 지우고 또 그 디렉토리를 만들어 주는 등등.

https://www.google.co.kr/search?q=dos+cmd+completely+delete+sub+director&ie=utf-8&oe=utf-8

위 검색결과를 보면 알겠지만 깔끔하고 만족스러운 방법이 없다.

그래서 Perl을 항상 깔아 쓰는 나는 이럴때 그냥 Perl로 하기로 했다.
Perl one-liner 를 써서 File::Path 모듈을 이용해서 하면

perl -MFile::Path=remove_tree -e "remove_tree('.\some_directory', { keep_root=>1 })"

2014/04/07

Perl의 다단계 deep hash와 autovivification

Perl에서 autovivification은 어떤때는 아주 강력한 장점이 되기도 하지만 잘 모르고 쓰다가는 찾기 힘든 버그를 만들 수 도 있다.

autovivification은 Perl의 해시구조에서 해시키가 존재하지 않을 경우 자동으로 생성해주는 기능으로 잘 활용하면 다음과 같은 집계작업을 아주 쉽게 끝낼 수 있다.

* 과일을 색깔별로 몇개씩 있는지 집계하는 코드

use strict;
use Data::Dumper;
my %h;
while (<DATA>) {
    chomp;
    my ($color, $fruit) = split;
    $h{$color}{$fruit}++;
}
print Dumper(\%h);

__DATA__
green apple
red apple
red strawberry
green banana
green apple
yellow banana
red apple
green apple
yellow apple
green strawberry

* 결과

$VAR1 = {
          'red' => {
                     'apple' => 2,
                     'strawberry' => 1
                   },
          'yellow' => {
                        'banana' => 1,
                        'apple' => 1
                      },
          'green' => {
                       'strawberry' => 1,
                       'banana' => 1,
                       'apple' => 3
                     }
        };


하지만 다단계 hash를 사용할때 가장 실수하기 쉬운 것은 만약에 hash키의 존재로 판단해야 하는 작업시 exists로 해당 해시키가 존재하는지 채크하게 되는데

if (exists $h{a}{b}) { say "exists"; }

처럼 $h{a} 즉 a키가 존재하지 않는데 이런식으로 하위 키에 대해 바로 존재여부를 채크하게 되면 중간 단계 a가 autovivification 기능에 의해 예기치 않게 생성되어 버린다. 그래도 문제 없을 경우도 있겠지만 만약에 첫 단계 해시키의 존재유무로 어떤 작업하는 로직이 같은 프로그램 안에 있다면 언제 용이 튀어 나올지 모른다. 그래서 이런 동작을 방지하려면

if (exists $h{a} && exists $h{a}{b}) { say "exists"; }

처럼 단계적으로 채크해 들어가야 한다.  그런데 여러단계면 이런식으로 계속적으로 채크 코드를 나열하는건 손가락이 아픈 일이라 생각되어 예전에 언뜻 본 autovivification 여부를 조정할 수 있는 autovivification 모듈을 사용하면 편하지 않을까 싶어 한 번 테스트를 해보았다.

아래 코드는 if 문의 판단부분에 do블럭으로 autovivification으로 비활성화 시키고 해시키 여부 판단코드를 쓴것과 다단계 채크 방식의 속도를 비교했고, 추가로 매번 if문 안에서 비활성화 시키지 않고 기본으로 비활성화 시킨상태에서 비교한 것이다.


#!/usr/bin/env perl
use 5.010;
use strict;
use warnings;
use autovivification;
use Benchmark qw/cmpthese/;

cmpthese(1000000, {
    step => sub {
        my %h;
        if (exists $h{a} && exists $h{a}{b}) { say "exists"; }
    },
    autovivification => sub { 
        my %h;
        if (do { no autovivification; exists $h{a}{b} }) { say "exists"; }
    },
});
no autovivification;
# 이 이하는 autovivification 비활성화
cmpthese(1000000, {
    step2 => sub {
        my %h;
        if (exists $h{a} && exists $h{a}{b}) { say "exists"; }
    },
    autovivification2 => sub { 
        my %h;
        if (exists $h{a}{b}) { say "exists"; }
    },
});
.

벤치마크 결과는 다음과 같다.

            (warning: too few iterations for a reliable count)
                      Rate autovivification             step
autovivification  265957/s               --             -92%
step             3194888/s            1101%               --
            (warning: too few iterations for a reliable count)
                       Rate autovivification2             step2
autovivification2  348432/s                --              -89%
step2             3194888/s              817%                --


다단계로 채크해 들어가는게 autovivification 모듈을 사용하는 것 보다 10배 가까이 빠른 속도를 보여준다.

결론:
 속도가 중요하다면 손가락이 아파도 해시키의 존재 여부는 다단계로 채크하자.

2013/12/03

2013년 Advent calendar들


올해도 어김없이 찾아온 Perl Korea Advent Calendar 2013

http://advent.perl.kr/2013/

전 세계적으로도 다른 Perl관련 Advent Calendar들이 시작되었음

Perl Advent Calendar
http://www.perladvent.org/2013/

Perl6 Advent Calendar
http://perl6advent.wordpress.com/

Catalyst Advent Calendar
http://www.catalystframework.org/calendar/

Dancer Advent Calendar
http://advent.perldancer.org/2013

Future Advent Calendar
http://leonerds-code.blogspot.co.uk/search/label/advent

일본 Perl Advent Calendar
http://qiita.com/advent-calendar/2013/perl

일본 Mojolicious Advent Calendar
http://qiita.com/advent-calendar/2013/mojolicious


그외에도 
Sysadmin Advent Calendar
http://sysadvent.blogspot.kr/

일본에서는 오픈소스 커뮤니티에 Advent Calendar 열풍이 불어서
Dog이나 Cow나 Advent Calendar를 만들고 있음
http://qiita.com/advent-calendar/2013



2013/06/21

PERL_NEW_COPY_ON_WRITE on Windows Perl.

I've found PERL_NEW_COPY_ON_WRITE option at https://metacpan.org/module/DAGOLDEN/perl-5.19.1/pod/perldelta.pod#Performance-Enhancements

This feature was already available in 5.18.0, but wasn't enabled by default.

So I recompiled Perl 5.18.0 with PERL_NEW_COPY_ON_WRITE option on Windows and compared with Non-PERL_NEW_COPY_ON_WRITE Strawberry Perl 5.18.0.

This is the result.


<Test code>

perl -MBenchmark=cmpthese -e "sub A {my ($s) = @_; length($s) } sub B { my ($s) = @_; $s .= 'B'; length($s) } cmpthese(10000, { test1 => sub { my $s = 'A'x1000000; A($s) }, test2 => sub { my $s = 'A'x1000000; B($s) } })"

"sub A" doesn't modify the input string but "sub B" modfies the input string.

* Strawberry Perl 5.18.0

test1  3374/s    --   -2%
test2 3428/s    2%    --

* PERL_NEW_COPY_ON_WRITE version

test1  8905/s  539%    --
test2 1393/s    --  -84%




PERL_NEW_COPY_ON_WRITE option accelerates "sub A" but slows down "sub B".
I'm wondering why "sub B" is much slower in PERL_NEW_COPY_ON_WRITE option.


2013/06/17

Hooking MS windows messages on Wx Perl.

Sometimes you may need to catch a Windows message that is not already handled by wxWidgets, so there is no Wx::Event for it. With a bit of help from the WIN32::API modules it is possible to hook into the WndProc chain for a wxWindow and watch for the message you are interested in.

The magic is in the SetWindowLong function. When used with the GWL_WNDPROC flag it causes a new WndProc to be set for the window, and returns the old one. This lets you write a function in Perl that can get first crack at all the Windows messages being sent to the window, and if you are not interested in them then pass them on to the original wxWidgets WndProc.

#!/usr/bin/env perl
use strict;
use Win32::API;
use Win32::API::Callback;
use Wx;

# Perl port of Python Code: http://wiki.wxpython.org/HookingTheWndProc

{

    package MyFrame;
    use base 'Wx::Frame';

    use constant GWL_WNDPROC => -4;

    # LONG  SetWindowLong(HWND hWnd, int nIndex, LONG dwNewLong);
    Win32::API->Import('user32', 'SetWindowLongW', 'NIK', 'N');
    # LRESULT CallWindowProc(WNDPROC lpPrevWndFunc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
    Win32::API->Import('user32', 'CallWindowProcW', 'NNIII', 'N');

    sub new {
        my $ref = shift;
        my $self = $ref->SUPER::new( undef,           # parent window
            -1,              # ID -1 means any
            'wxPerl rules',  # title
            [-1, -1],        # default position
            [150, 100],      # size
        );
        # controls should not be placed directly inside
        # a frame, use a Wx::Panel instead
        my $panel = Wx::Panel->new( $self,            # parent window
            -1,               # ID
        );
        # create a button
        my $button = Wx::Button->new( $panel,         # parent window
            -1,             # ID
            'Click me!',    # label
            [30, 20],       # position
            [-1, -1],       # default size
        );
        $self->{newWndProc} = Win32::API::Callback->new(sub { $self->_MyWndProc(@_) }, 'NIII', 'N');
        $self->{oldWndProc} = SetWindowLongW( $self->GetHandle(), GWL_WNDPROC, $self->{newWndProc} );

        return $self;
    }
    
    sub _MyWndProc {
        my ($self, $hWnd, $msg, $wParam, $lParam) = @_;
        # You can process MS Windows messages here.
        print join (',',@_),"\n";
        CallWindowProcW($self->{oldWndProc}, $hWnd, $msg, $wParam, $lParam);
    }

}

{

    package MyApp;
    use base 'Wx::App';

    sub OnInit {
        my $frame = MyFrame->new;
        $frame->Show( 1 );
    }

}

my $app = MyApp->new;
$app->MainLoop;



2013/05/19

Perl 5.18.0 출시

2013년 5월 18일 Perl 5.18.0 버젼이 출시되었다. 5.18은 우리 현대사에서도 의미 있는 날인데 Perl 5.18.0 버젼을 5월 18일에 내놓는 센스를 발휘하다니...

Perl 5.18.0 에서 바뀐 내용들은 https://metacpan.org/module/RJBS/perl-5.18.0/pod/perldelta.pod 에 잘 정리되어 있다.

5.18.0버젼은 5.16.0 버젼이후 1년동안의 개발기간 동안 113명의 개발자에 의해 2100개의 파일, 400000라인의 수정/추가가 이루어 졌다고 한다.

중요한 점만 짚어 보자면..

Unicode 6.2를 지원.

예전에는 암묵적으로 허용되었던 foreach my $item qw/A B C/ { ... } 같은 코드는 ( )를 명시적으로 싸서 foreach my $item (qw/A B C/)  { ... } 처럼 해 주지 않으면 에러가 난다.  

given ~ when 구문과 ~~스마트매치 연산자가 실험적기능(experimental)으로 빠졌다. 그동안 너무 복잡하고 설계에 문제점이 있다고 말이 많았는데 이 기회에 일단 빼놓고 다시 검토할 계획인가 보다. 5.18에서는 해당 문법을 쓰면 경고가 난다.
 참고: https://metacpan.org/module/RJBS/perl-5.18.0/pod/perldelta.pod#The-smartmatch-family-of-features-are-now-experimental

그리고  encoding, Archive::Extract, CPANPLUS 등 코어모듈에서 제외된 모듈들이 제법 있다.
 참고: https://metacpan.org/module/RJBS/perl-5.18.0/pod/perldelta.pod#Module-removals

또 hash의 취약점 공격을 막기 위해 hash에서 값을 뽑아낼때 마다 랜덤하도록 바뀌었다. 이 때문에 테스트에서 에러가 나는 모듈이 종종 있다.  특히 많이 쓰는 JSON::XS모듈도 그 중 하나인데 패치 되기전에 일단 cpan -T JSON::XS 나 cpanm -n JSON::XS 명령으로 테스트과정을 생략하고 설치해서 쓰도록 하면 된다고 한다.(JSON::XS 모듈은 2.34버젼에서 고쳐졌음)
 참고: http://blog.twoshortplanks.com/2013/05/20/5-18-hash-keys/


마지막으로 Perl 5.18.0 버젼은 안타깝게도 우리 곁을 일찍 떠나버린 @am0c 군을 추모하는 글( https://metacpan.org/module/RJBS/perl-5.18.0/pod/perldelta.pod#Obituary ) 이 포함되어 더욱 뜻깊은 버젼이기도 하다.