2015/02/04

Perl 6를 슬슬 써보자.

Perl창시자 Larry Wall이 FOSDEM 2015Get ready to party! 라는 세션에서 2015년 9월 자신의 생일까지 Perl6의 베타판 12월25일 크리스마스에 정식판을 내놓는다고 선언했다.

그동안 Perl6는 10년이 넘도록 정식안정 버젼이 나오지 않아 프로그래밍언어계의 duke nukem forever라는 조롱을 듣기도 하고 언제 나오나요?라는 물음에는 Larry Wall은 크리스마스(몇년도 크리스마스인지는 특정하지 않음)에 나옵니다. 하는 농담으로 넘어가곤 했는데. 이번에는 딱 찝어서 2015년 크리스마스에 정식버젼을 내놓는다고 선언을 함으로써 더 이상 미룰 수 없게 되었다. 그 배경에는 내부적으로 개발수준이 프로덕션레벨에 근접했다는 자신감이 있었기 때문이 아닌가 생각된다.

다가올 Perl6를 대비해서 슬슬 테스트해보면서 써보려면 다음과 같은 과정을 거치면 쉽다.

perlbrew 같이 perl버젼 메니저 역할을 해주는 rakudobrew를 다음과 같은 과정으로 설치한다.

$ git clone https://github.com/tadzik/rakudobrew ~/.rakudobrew

그리고 .bashrc 같은 쉘초기 설정 파일에 다음과 같은 라인을 추가하고 쉘을 재시작해준다.

export PATH=~/.rakudobrew/bin:$PATH

그 다음

$ rakudobrew build moar

로 moar VM기반으로 rakudo perl6를 설치한다.
(현재 Perl6의 기반VM은 최초의 구현체인 Parrot 말고도 JVM기반과 Perl6객체에
최적화된 moarvm 3종류가 지원되고 있다.)

그 다음 아래 명령으로 Perl6 모듈관리 명령인 panda를 설치한다.

$ rakudobrew build-panda

이제 Perl6를 사용할 준비가 되었다. 동작하는지 실행해보자!
$ perl6 -e 'say "Hello, World"'
Hello, World

$ perl6 -e '([+] 1..1000).say'
500500
 
잘된다! Perl6는 요즘 Node.js쪽에서도 HOT한 것 같은 Promise, Future같은것도 지원하니 아래의 링크들을 보면서 참고해서 테스트해 보면 될듯하다.



*Perl6 관련 볼만한 링크몇개

Learn Y in X Minutes post for Perl 6
http://learnxinyminutes.com/docs/perl6/

Getting beyond static vs. dynamic
http://jnthn.net/papers/2015-fosdem-static-dynamic.pdf

Adventures in Perl 6 Asynchrony
http://jnthn.net/papers/2014-yapceu-async.pdf

Perl 6 For Mere Mortals
http://www.slideshare.net/Ovid/perl-6-for-mere-mortals

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;   # 같은 패키지 내부면 my $b = \&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;