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배 가까이 빠른 속도를 보여준다.

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