manywaypark's Blog
개발, 검색, 함수

제목의 용어가 정확한지 잘 모르겠다. 작업도중 필요해서 만들게 되었는데 함수형 언어의 장점(REPL의 장점)을 부각시킬 수 있는 예제가 될 수 있을 것같아서 포스팅한다.
전각 문자를 반각으로 문자열 변환하는 간단한 함수를 하나 만들었는데 좀 꼼수(?)를 동원해서 얄팍하게 재빨리 만들었다.
정확하게 하려면 몇몇 자료를 참조해야하겠지만, 필요한 상황의 테스트 케이스는 다 통과하므로 그대로 쓰기로 했다.

(emacs@desktop)1> "ABC012". % wide chars
"\357\274\241\357\274\242\357\274\243\357\274\220\357\274\221\357\274\222".
[239,188,161,239,188,162,239,188,163,239,188,144,239,188,
 145,239,188,146]
(emacs@desktop)2> "A".         % wide char
"\357\274\241".
(emacs@desktop)3> "A".          % normal char
"A"
(emacs@desktop)4> "0".         % wide char
"\357\274\220".
[239,188,144]
(emacs@desktop)5> $0.           % normal char
48

패턴이 보이는가?
[239,188] ("\357\274") 뒤의 숫자만 변한다. ^^;
자연스럽게 다음과 같은 함수가 만들어진다.
%% 96 = last("1") - $1
'wide->narrow'([239,188,C|T]) ->
    [C - 96 | 'wide->narrow'(T)];
'wide->narrow'([C|T]) ->
    [C | 'wide->narrow'(T)];
'wide->narrow'([]) ->
    [].

결과:
(emacs@desktop)8> str_util:'wide->narrow'("ABC012").
str_util:'wide->narrow'("\357\274\241\357\274\242\357\274\243\357\274\220\357\274\221\357\274\222").
"ABC012"

REPL이 지원되지 않는 언어였더라면 과연 이렇게 눈깜짝할 사이에 만들 수 있었을까?
단위 작업의 feedback이 즉시 확인되는 것은 엄청난 매력이자 장점이다.

happy hackin'
좀 복잡한 테스트 케이스를 하나 추가하고 컴파일후 mod:test()를 실행했더니,
다음과 같은 에러가 나면서 그 모듈에 대한 유닛 테스트가 아예 동작하지 않았다.
*** test module not found ***
::false

{error,{module_not_found,false}}

순간 당황했으나, 방금 추가한 테스트 케이스를 살펴보니 _assert macro에 들어가는 내용이 잘못되어 있었다. boolean expression이 들어가야하는 곳에 그냥 expression만 하나 들어있었고, _assert(very_complex_expression) =:= expected value의 형태로 되어 있었다(어제 급히 추가하고 안돌려보고 바로 퇴근한 것이 후회되는 순간이었다). 그래도 컴파일은 멀쩡히 평소처럼 잘 되고 runtime에 가서야 에러가 나는 것은 좀 불편해보인다.

간략히 축약해서 예를 들자면 다음과 같다.
%% causes runtime error (see above msg).
?_assert(1+1) =:= 2
%% runs well w/o runtime error (good)
?_assert(1+1 =:= 2)

실제 내 코드에서는 1+1부분이 많이 복잡해서 화면을 넘어가고 있었고, _assert macro가 여러 개의 리스트로 묶여져 있어서 찾는데 좀 고생했다. Orz.

happy hackin'
외부 DTD 파일에 <?xml version ... ?> 같은 XML 파일임을 나타내는 PI가 들어있으면 xmerl_scan:(file|string)에서는 다음과 같은 에러를 내면서 parsing이 안된다 (java 쪽에서는 별 문제가 없었던 DTD 파일이다).

error msg:
1066- fatal: {invalid_target_name," version=\""}
** exception exit: {fatal,{{invalid_target_name," version=\""},
                           {file,"/path/to/dtd.dtd"},
                           {line,6},
                           {col,18}}}
     in function  xmerl_scan:fatal/2
     in call from xmerl_scan:scan_ext_subset/2
     in call from xmerl_scan:scan_decl_sep/2
     in call from xmerl_scan:scan_ext_subset/2
     in call from xmerl_scan:scan_decl/2
     in call from xmerl_scan:fetch_and_parse/3
     in call from xmerl_scan:fetch_DTD/2
     in call from xmerl_scan:scan_doctype2/3

간편 해결책: DTD 들이 있는 디렉토리에서 다음을 실행해주자 (DTD 파일이 여러개일때...)
$ sed -i -r "/<\?xml .*\?>/s/.*//" *.dtd      # remove xml PI stuff.
$ sed -i '/./,/^$/!d' *.dtd                   # remove leading blank line(s).

happy hackin'
ko:
성능을 향상 시키는 방법:
  1. 메모리 사용량을 낮게 유지한다.
  2. CPU 사용률을 높게 유지한다.
  3. 한계를 넘어서지 않는 한 많은 수의 worker 를 동작시킨다. (2번을 실제 적용하는 유용한 방법중의 하나).
  4. 실행 흐름을 동기화가 필요하지 않은 방향으로 설계한다 (spawn후에 잊어버리기 전략. 3번과 일맥상통).

en:
to improve performance of concurrent program:
  1. always keep small footprint of memory usage.
  2. high CPU utilization.
  3. run workers as many as possible (of course, under limit). one of good methods for #2.
  4. design program flow to avoid synchronization. use spawn and forget strategy if possible.

happy hackin'

[FUN] Spiral Array

함수형 언어/Erlang 2008. 8. 13. 18:11 by manywaypark
여기에서 호기심을 조금 느꼈는데, 링크된 여기를 보고는 완전 몰입.

문제를 푸는 것자체는 그다지 어렵지 않았으나, 멋있게 풀어보려고, 오늘 출근길 지하철에서 타블렛에 좀 끄적였다.
W by H 행렬이라 보고, top left에서, 시계방향, 반시계방향 Spiral Array를 출력해주는 것을 만들었다.
(물론 요즘은 erlang에 심취해있으므로,  구현언어는 당연히 erlang이다.)

요지는 조건 검사 같은 것을 하는 것이아니라, 수열을 사용한다.
즉, 동일한 상태 이동의 반복 회수가 수열이다. 연속된 transition의 개수를 구하는 함수를 f(x)라 하면,
f(x) = f(x - 2) - 1

x가 2보다 크면 무조건 성립이고, x = 1, 2 일때의 값은 clockwise, counterclockwise에 따라 W 또는 H로 조금 달라지기는 하지만 중요한 요점은 수열을 이용해 계산으로 구한다는것이다. 자세한 건 코드 참조!

성능은 1000 x 1000의 결과 구하는데 3초정도 걸리는 것같다.

실행예:
(emacs@desktop)2> sa:do(5,4).
   1    2    3    4    5
  14   15   16   17    6
  13   20   19   18    7
  12   11   10    9    8
ok
(emacs@desktop)3> sa:do(4,5).
   1    2    3    4
  14   15   16    5
  13   20   17    6
  12   19   18    7
  11   10    9    8
ok
(emacs@desktop)5> element(1, timer:tc(sa,solve, [1000,1000])) / 1000000.
3.126565

code listing:

2008-08-18 : 거의 동일한 방법으로 푼 사람이 이미 있었다. 맨앞에 몇개와 끝부분의 erlang 버전만 확인하고서 획기적인 방법이라고 좋아라하고 있었다니.... Orz. 그래도 high order function을 사용해서 functional에 걸맞는 멋진 프로그램이라고 나 자신에게 조금은 구차한(?) 최면을 거는 중이다.

happy hackin'

일단 eunit으로 테스트를 하는 것을 가정한다.

unit test - makefile에서...
make file에 다음과 같은 부분을 추가한 후에, make test 하면 간편하게 명령행에서 unit test를 수행할 수있다.
(makefile 뼈대는 Programming Erlang을 기초로 했다.)

......
MODS = doc str_util db i18n tiny_scan
......

test: compile test_subdirs
    rm -rf Mnesia.nonode@nohost # removes previous mneisa db (optional).
    @for m in ${MODS};\
    do \
        echo "testing $$m";\
        ${ERL} -noshell -pz "subdir1" -pz "subdir2" -s $$m test -s init stop;\
    done

test_subdirs:
    cd subdir1; make test
    cd subdir2; make test

unit test, coverage test - 개발중 REPL(distel 또는 erl prompt) 에서...
REPL을 사용해서 (멋지게) 코딩-테스트를 반복중이라면, 다음과 같은 모듈을 하나 디렉토리에 넣어서 test:unit() 또는 test:cover() 를 실행시켜서 unit test 및 coverage test를 시시때때로 수행해 볼 수 있다.


테스트는 아무리 강조해도 지나치지 않다.
경험상 대부분의 버그는 테스트되지 않거나, 적절하지 못한 테스트(셋)들을 통과한 코드에 기생한다.

happy hackin'



해당 version: R12B-3

xmerl을 사용하여 다량의 xml 파일들을 파싱하는 도중에 특정 파일에서 CPU/메모리사용량이 치솟고 다음과 같은 에러메시지와 함께 erlang이 죽어버렸다.

eheap_alloc: Cannot allocate xxxxx bytes of memory (of type "heap").

특정 파일 하나에서만 이 현상이 생겼는데, 파일을 열어보니 복잡한 테이블과, entity들을 많이 사용한 xml이었다. 조금 복잡하긴 했지만 정상적인 xml이었다.

검색한 결과 xmerl_scan의 버그임이 밝혀졌다.
(R12B-4 에서는 패치된 버전이 포함될 것이라고 한다).

링크에 나와있는대로 한줄만 고쳐주면 에러없이 잘 동작한다.



happy hackin'

erlang build 하기

함수형 언어/Erlang 2008. 7. 29. 23:22 by manywaypark
압축풀고 README에 있는 프로그램들 깔려있는지 확인하고,
$ ./configure --enable-hipe --enable-threads
$ make
$ sudo make install

(odd) case error of
checking for C compiler default output file name... configure: error: C compiler cannot create executables
See `config.log' for more details.
-> install libc (apt-get install libc6-dev).
end.

2008-11-01: 간만에 새로 ubuntu를 설치한 박스에서 erlang을 build하려고 시도하니, 필요한 패키지가 정확하게 드러났다.
현재 최신의 OTP-R12B-4를 빌드할 때 모든 기능을 활성화 하기위한 패키지들은 다음과같다.
m4
autoconf
g++
libc6-dev
libncurses5-dev
unixodbc-dev
libssl-dev

happy hackin'
erlang의 one-time assignment를 이용한 간단한 리팩토링에 관해서 설명한다.

erlang에서의 변수 특성:
  1. erlang은 변수에 값(value)를 한번만 할당(assign)할 수 있다.
  2. 한번 값이 할당된 변수는 이전과 동일한 값을 할당하지 않으면 에러가 난다.

확인:
(emacs@desktop)54> Foo = 1.
1
(emacs@desktop)55> Foo = 1.
1
(emacs@desktop)56> Foo = 2.
** exception error: no match of right hand side value 2

리팩토링은 보통 코드의 결과는 그대로 유지하면서 내부를 개선하는 것으로 정의된다.

변수 특성을 이용한 리팩토링 절차:
  1. 리팩토링할 함수를 정한다.
  2. 함수의 결과값을 변수에 저장한다. (적절한 인자를 줄 수도 있다)
  3. 함수 리팩토링 & 컴파일
  4. 같은 변수에 새 함수의 결과값을 대입해본다.
  5. 에러 체크.

간단한 함수의 경우는 이 방법이 별 필요가 없을 수도 있지만, 수백 수천개의 list element들을 결과로 리턴하는 경우에는 꽤나 유용하다.

각각 다른 변수(Before, After)에 대입해서 =:= 연산자로 비교할 수도 있겠지만, 그냥 같은 변수에 대입해 보는 것이 조금 더 편하다. (자연스런 REPL loop + history 기능 활용)

물론 여기서 설명한 것은 간단히 빨리 테스트해보아야할 경우를 위한 것이다. 실제 제품개발에는 eunit같은 unit test tool을 적용해 조직화된 테스트셋을 구성해야한다.

happy hackin'

xml 파일을 저장하기위해 다음과 같은 함수를 만들었다.
save_xml(Path, RootEl) ->
    {ok,IOF}=file:open(Path,[write]),
    Export=xmerl:export_simple([RootEl], xmerl_xml),
    io:format(IOF,"~s~n", [lists:flatten(Export)]),
    file:close(IOF).

에러가 났다.
83> foo:save_xml("/tmp/foo.xml", Root).
** exception exit: {badarg,[{io,format,
                                [<0.3348.0>,"~s~n",
                                 [[60,63,120,109,108,32,118,101,114,115,105,
                                   111,110,61,34,49,46,48,34|...]]]},
                            {erl_eval,do_apply,5},
                            {shell,exprs,6},
                            {shell,eval_exprs,6},
                            {shell,eval_loop,3}]}
     in function  io:o_request/2

이거 분명히 예전에 공부할 때 예제가 동작하는 것을 확인한 함수였는데....
binary로 써보기로했다.
84> Export=xmerl:export_simple([Root], xmerl_xml).
      ........
85> file:write_file("/tmp/1.xml", list_to_binary(lists:flatten(Export))).
** exception error: bad argument
     in function  list_to_binary/1
        called as list_to_binary([60,63,120,109,108,32,118,101,114,115,105,
                                  111,110,61,34,49,46,48,34,63,62,60,111,108,
                                  100,114,103,112|...])

한글이 포함되어 안되는 것으로 짐작이 갔다.
99> list_to_binary([0, 1, 2]).
<<0,1,2>>
100> list_to_binary([0, 1, 2, 255]).
<<0,1,2,255>>
101> list_to_binary([0, 1, 2, 255, 256]).
** exception error: bad argument
     in function  list_to_binary/1
        called as list_to_binary([0,1,2,255,256])

결론은 list_to_binary가 255를 넘어가는 non-ascii를 제대로 처리하지 못해서였다.
102> list_to_binary(xmerl_ucs:to_utf8([0, 1, 2, 255, 256])).
<<0,1,2,195,191,196,128>>
103> file:write_file("/tmp/1.xml", list_to_binary(xmerl_ucs:to_utf8(lists:flatten(Export)))).
ok

맨 처음 함수는 이런 식으로 변경:
save_xml(Path, RootEl) ->
    {ok,IOF}=file:open(Path,[write]),
    Export=xmerl:export_simple([RootEl], xmerl_xml),
    io:format(IOF,"~s~n", [xmerl_ucs:to_utf8(lists:flatten(Export))]),
    file:close(IOF).

참고: http://intertwingly.net/blog/2007/09/14/ASCII-ISO-8859-1-UCS-and-Erlang

happy hackin'
1 2 3 4 
분류 전체보기 (306)
잡담 (20)
함수형 언어 (65)
emacs (16)
java (18)
tips & tricks (154)
사랑 (1)
가사 (0)
독서 (4)
mobile (6)
비함수형 언어 (2)

공지사항

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

05-01 19:56