Languages/Erlang2009.06.23 14:07
Erlang과 Java를 연동시키려면 Erlang의 runtime을 띄울 때 epmd가 구동되도록 해야 합니다. 별도로 실행 방법을 알 필요는 없고, erl을 띄울 때 node 이름과 host name을 지정해 주면 됩니다.

$> erl -name foo@foo.bar

그렇게 하면 epmd가 자동으로 듭니다. 그래야 나중에 Java가 붙을 때 Java 프로그램의 node 이름이 해당 epmd에 등록 되기 때문에, 반드시 이렇게 해 주어야 합니다.

Erlang과 Java 프로그램의 연동은 서로 메시지를 주고 받는 과정을 통해서 이루어집니다. 따라서 연동을 하려면 Java 프로그램과 연동할 Erlang 프로세스가 떠 있어야 합니다. 다음의 간단한 프로그램 예제를 봅시다. 이 파일을 ets_java_frontend.erl로 저장하고 컴파일 해 둡니다.

-module(ets_java_frontend).
-export([start/0, shutdown/0, show/0, rpc/1]).

start() ->
    LoopPid = spawn(fun loop/0),
    register(ets_server, LoopPid).

shutdown() ->
    Pid = whereis(ets_server),
    Pid ! quit.

show() ->
    Pid = whereis(ets_server),
    Pid ! show.

rpc(T) ->
    Pid = whereis(ets_server),
    Pid ! T.

loop() ->
    EtsHandle = ets:new(test, [set]),
    loop(EtsHandle).

loop(Handle) ->
    receive
        show ->
            List = ets:tab2list(Handle),
            io:format("~p~n", [List]),
            loop(Handle);
        quit ->
            {ok, terminated};
        {tuple, T} ->
            io:format("msg is received ~p~n", [T]),
            ets:insert(Handle, T),
            loop(Handle);
        true ->
            io:format("unprocessable msg is received~n"),
            loop(Handle)
    end.

이 Erlang 프로그램은 {tuple, T} 형태의 메시지를 받으면 T (투플입니다)를 ets에 저장하는 프로그램입니다. spawn해서 프로세스를 생성할 때 해당 프로세스의 Pid를 register() 호출을 통해 등록하고 있는데, Java쪽에서 이 Erlang 프로그램에 메시지를 보낼 때 register()할 때 전달한 프로세스 이름(위의 경우에는 ets_server)을 통해 해당 프로세스의 mbox에 메시지를 전달하므로, register()를 반드시 호출해 프로세스 이름을 등록해 두어야 합니다.

이렇게 하면 Java 프로그램 안에서 Erlang 프로세스에 메시지를 전달할 수 있는데요. 공짜로 되는 것은 아니고 (당연하겠죠?) Erlang 패키지에 포함되어 있는 JInterface라는 라이브러리를 통하여야 합니다. Windows라면 해당 라이브러리는 C:/Program Files/erl5.7.2/lib/jinterface-1.5.1/priv에 있고, Unix라면 아마 /usr/lib/erlang/ 아래 어딘가에 해당 jar가 있을 겁니다. ^^;

잡소리는 집어치우고 해당 프로그램 예제를 간단하게 훑어보면...

import java.io.IOException;
import com.ericsson.otp.erlang.*;

public class ETP {

    private String node;
    private OtpNode self;
    private OtpMbox mbox;

    /**
     * create ETP instance.
     * @param name name of the erlang server node.
     * @throws IOException
     */
    public ETP(String node) throws IOException {
       
        this.node = node;
       
        try {
            this.self = new OtpNode("java_erl_bjlee");
        } catch ( IOException e ) {
            System.err.println("cannot create ETP instance.");
            e.printStackTrace();
            throw e;
        }

        this.mbox = self.createMbox("etp_java_client");
    }

    private OtpErlangTuple createMessage(OtpErlangTuple tuple) {
       OtpErlangObject[] aTerm = new OtpErlangObject[2];
       aTerm[0] = new OtpErlangAtom("tuple");
       aTerm[1] = tuple;
       OtpErlangTuple msg = new OtpErlangTuple( aTerm );
       return msg;
   }

   private void send(OtpErlangTuple msg) {
       mbox.send("ets_server", this.node, msg);
   }
   
    public boolean put(String key, String value) {
        OtpErlangObject[] aTuple = new OtpErlangObject[2];
        aTuple[0] = new OtpErlangAtom(key);
        aTuple[1] = new OtpErlangAtom(value);
        OtpErlangTuple tuple = new OtpErlangTuple(aTuple);
       
        OtpErlangTuple msg = createMessage(tuple);
       
        send(msg);
       
        return true;
    }
}

this.node에는 통신할 Erlang 노드의 이름이 들어갑니다. 이 페이지 맨 위에서처럼 했다면 foo가 들어가야겠군요. 먼저 OtpNode를 생성하구요. (이름을 주어야 합니다. Java 프로그램은 별개의 Erlang 노드인 것처럼 동작하거든요.) 그 다음에 message 송수신에 사용될 mbox를 만듭니다.

그 다음에는 이제 메시지를 보내 Erlang 프로그램이 원하는 작업을 하도록 만들면 됩니다. put 메소드의 코드를 보시면 되겠습니다. mbox.send() 함수가 최종적으로 호출되는데, 이 때 첫 번째 인자로는 통신할 erlang 프로세스의 registered name이 들어가고, 두 번째 인자로는 해당 프로세스가 돌고 있는 Erlang node, 그리고 세 번째 인자로는 실제로 전송할 메시지가 들어갑니다.

이제 Main 클래스를 다음과 같이 작성하고 돌려보면...

public class Main {

    /**
     * @param args
     */
    public static void main(String[] args) {
       
        ETP etp;
       
        try {
            etp = new ETP("foo");
        } catch (IOException e) {
           
            e.printStackTrace();
            return;
        }
       
        etp.put("test1", "value1");
        etp.put("test2", "value2");
       
        System.out.println("finished");
    }
}

참. 돌리기 전에 erl 셸에서 ets_java_frontend:start()를 먼저 실행해주어야 합니다. ㅎㅎ 어쨌든 실행하고 위의 Java 프로그램을 돌려보면... erl 쪽 화면에 메시지들이 찍히면서 ets에 내가 전송한 투플들이 저장됩니다. 저장이 잘 되었는지는 erl 셸에서 ets_java_frontend:show()를 실행하여 확인할 수 있습니다.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 땅꼬마

    감사합니다, 좋은 정보 얻어가네요 ^^

    2009.12.24 10:28 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Erlang2008.09.22 17:19

앞선 글을 통해 Erlang에서 병렬 프로그래밍이 어떻게 이루어지는지 대략이나마 감을 잡으실 수 있었을 거라고 믿습니다. 다시 정리해보자면, 얼랭에서 병렬 프로그램은 프로세스의 집합으로 만들어지고, 프로세스 끼리 메시지를 주고받아 통신하는 방식을 사용해 구현됩니다. 메시지를 보낼때는 Pid ! Message 이런 형식으로 보내고, 받는 쪽에서는 receive 를 사용해 메시지를 받아 처리합니다.

그러면 지금부터는 앞선 글에서 만든 프로그램을 진짜 클라이언트/서버 모델을 만족하도록 바꿔 보겠습니다. 클라이언트가 서버에게 요청을 보내면, 서버는 그 요청을 해석해서 클라이언트에게 답을 보내주어야 합니다. 다음의 코드를 보시죠.

-module(concur).
-export([start_server/0, send_request/2, send_stop/1]).

start_server() -> spawn( fun loop/0 ).

loop() ->
    receive
        {request, Pid, L} ->
            Result = odds_and_evens_acc(L),
            Pid ! {response, self(), Result},
            loop();
        stop ->
            exit
    end.

send_request(Pid, L) ->
    Pid ! {request, self(), L},
    receive
        {response, _SPid, {{odd, Ov},{even,Ev}}} ->
            io:format("odd: ~p, even: ~p~n", [Ov,Ev])
    end.

send_stop(Pid) -> Pid ! stop.

odds_and_evens_acc(L) ->
    odds_and_evens_acc(L, 0, 0).

odds_and_evens_acc([H|T], Odds, Evens) ->
    case ( H rem 2 ) of
        1 -> odds_and_evens_acc(T, Odds + H, Evens);
        0 -> odds_and_evens_acc(T, Odds, Evens + H)
    end;
odds_and_evens_acc([], Odds, Evens) ->
    { {odd, Odds}, {even, Evens} }.

클라이언트/서버 모델을 따르는 통신을 하기 위해, 서버에게 보내는 메시지의 형식이 좀 바뀌었습니다. 메시지가 투플 형태로 간다는 것은 종전과 똑같습니다만, 그 투플의 두 번째 항목에 요청을 보내는 클라이언트 프로세스의 PID가 들어간다는 점이 달라졌습니다. 이 PID가 있어야 서버는 어느 클라이언트에게 응답을 보내야 할 지를 알 수  있죠. 서버는 요청이 오면 odds_and_evens_acc를 호출해서 결과를 계산한 다음에, 요청을 보낸 프로세스에게 응답을 전송합니다. 전송되는 응답 투플 맨 앞에는 '응답'임을 알리기 위해 response라는 아톰이 들어가고, 투플 두번째 항목에는 서버 프로세스의 PID가 들어가고(self()를 호출하면 자기 자신의 PID를 얻을 수 있습니다) 세 번째 항목에는 보낼 계산 결과값이 들어갑니다. 

클라이언트는 서버에게 요청을 보내기 위해 send_request 함수를 이용해야 합니다. 이 함수의 첫번째 인자로는 서버의 PID가 오고, 두 번째 인자로는 리스트가 전달됩니다. send_request는 해당 인자들을 사용해 서버에 메시지를 전송한 다음, 스스로 receive를 통해 그 서버가 보내는 응답을 전송받습니다. 그런 다음 응답을 해석해 화면에 결과값을 뿌리죠. (서버가 보내는 PID는 사용하지 않을 것이기 때문에 _SPid와 같은 이름의 변수를 썼다는 것에 주의합시다. _가 앞에 붙는 변수는 설사 이후의 코드에서 사용되지 않아도 컴파일러가 ok합니다.)

위의 코드는 다음과 같이 사용합니다.

14> c(concur).
{ok,concur}
15> Pid = concur:start_server().
<0.90.0>
16> concur:send_request(Pid, [1,2,3,4,5,6]).
odd: 9, even: 12
ok
17>

이렇게 해서 클라이언트/서버 모델을 따르는 간단한 예제까지 만들어 봤습니다. 이해하기 쉬우셨는지 모르겠군요. 다음 글에서는 실제로 메시지 송수신 과정에서 발생할 수 있는 문제에 대처할 수 있는 코드를 만드는 방법을 포함해, 조금 더 심화된 주제들을 살펴보겠습니다.

머리가 터질거같은 상태에서 억지로 글을 쓰다보니 더 이상 길게는 못쓰겠군요. 머리 터질때 코드 작성하는 일 말고는 할 수 있는게 없는 저 자신도 참 한심하기 짝이 없습니다만....




신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Erlang2008.09.22 13:46

앞선 다섯 번의 글을 통해, Erlang의 가장 기본적인 면들을 살펴 봤습니다. 물론 다 살펴본 것은 아닙니다만, 미처 살펴보지 못한 주제들(binary, record...)은 Erlang의 가장 기본적인 부분이라기 보다는 양념에 가깝고, 특별히 심각한 프로그래밍을 할 생각이 없다면 일단은 제처놓고 사용하지 않을 수도 있습니다. 그러니, 나중에 필요할 때 더 살펴보도록 하죠.

그러니, 오늘은 바로 병행성(concurrency)으로 점프해 보도록 하겠습니다. 사실 얼랭을 배우겠다고 작심하신 분들 중 상당수는 Erlang의 병렬 처리 성능에 이끌려 오신 분들일 거에요. CPU를 꽂으면 꽂는 대로 성능이 graceful 하게 upgrader되는 시스템은 모든 개발자가 꿈꾸는 시스템이긴 합니다만, 그렇다고 모든 사람들이 그런 프로그램을 짤 수 있는 것은 아니었죠. (언어의 가상 머신 시스템이 발전되어 가면서 그 장벽이 점점 낮아지고 있는 것은 사실입니다만... )

Erlang의 병행성은 spawn이라는 fork() call과 메시지 send / receive 메커니즘을 톻해 얻어집니다. 그 메커니즘이 동작하는 방식이 굉장히 간단하고 알기 쉽기 떄문에, C에서 fork()를 써 보신 분이나, 병행 프로그래밍에 대해 아주 간단한 수준의 지식만 가지고 있더라도 프로그래밍을 할 수가 있어요.

우선, 예전에 만들었던 함수들을 가지고, 그 함수들을 사용해 병행성 프로그램을 만드는 실습을 한번 해 보겠습니다. 다음 코드를 보시죠.

-module(concur).
-export([odds_and_evens_acc/1]).

odds_and_evens_acc(L) ->
    odds_and_evens_acc(L, 0, 0).

odds_and_evens_acc([H|T], Odds, Evens) ->
    case ( H rem 2 ) of
        1 -> odds_and_evens_acc(T, Odds + H, Evens);
        0 -> odds_and_evens_acc(T, Odds, Evens + H)
    end;
odds_and_evens_acc([], Odds, Evens) ->
    { {odd, Odds}, {even, Evens} }.

이 함수를 컴파일하고 실행하면 다음과 같은 결과를 얻습니다.

Eshell V5.5.5  (abort with ^G)
1> c(concur).
{ok,concur}
2> concur:odds_and_evens_acc([1,2,3,4,5,6,7,8,9,10]).
{{odd,25},{even,30}}
3>

뭐 여기까지는 단순한데요. 자. 그러면 이 함수를 별개의 프로세스로 실행 시킬 수 있도록, 코드를 변경해 보도록 하죠. 목표는 odds_and_evens_acc 함수가 서버로서 계속해서 동작할 수 있도록 만드는 것입니다. 클라이언트 측에서는 이 서버에 메시지를 전달하는 것만으로 리스트의 홀수합과 짝수합을 계산할 수 있어야 하죠.

우선 서버라는 것은 뭔가가 계속해서 끊임없이 돌아야 하는 것이니까, 그 끊임없이 도는 함수를 한 번 만들어 보겠습니다. 메시지를 수신할 때 쓰는 receive 구문이 포함되어 있으니까 주의해서 보시기 바랍니다.

-module(concur).
-export([start/0]).

start() -> spawn( fun loop/0 ).

loop() ->
    receive
        L ->
            {{odd, Ov}, {even, Ev}} = odds_and_evens_acc(L),
            io:format("~p, ~p~n", [Ov, Ev]),
            loop()
    end.

odds_and_evens_acc(L) ->
    odds_and_evens_acc(L, 0, 0).

odds_and_evens_acc([H|T], Odds, Evens) ->
    case ( H rem 2 ) of
        1 -> odds_and_evens_acc(T, Odds + H, Evens);
        0 -> odds_and_evens_acc(T, Odds, Evens + H)
    end;


odds_and_evens_acc([], Odds, Evens) ->
    { {odd, Odds}, {even, Evens} }.

이 코드는 다음과 같이 실행합니다.

23> c(concur).
{ok,concur}
24> Q = concur:start().
<0.95.0>
25> Q ! [1,2,3].
[1,2,3]
4, 2
27>

concur 모듈에 정의되어 있는 start() 함수를 부르면, spawn 함수가 호출되면서 새로운 프로세스가 만들어지고, 그 프로세스의 PID가 반환됩니다. spawn 함수의 인자로 fun loop/0을 넘겼기 때문에, 해당 함수가 그 프로세스의 이미지(image)가 됩니다. 이 함수는 메시지가 도착할 때 마다 (이 메시지는 리스트입니다) 그 메시지를 인자로 하여 odds_and_evens_acc 함수를 호출하고, 그 결과를 화면에 출력합니다. 서버는 계속해서 돌아야 하기 때문에, 실행이 끝나면 loop()로 자기 자신을 호출하여 다음 메시지를 기다립니다. 간단하죠? 클라이언트 측에서는 이 서버에 메시지를 전송하려면 서버의 PID에다가 !를 주고 메시지를 인자로 넘기기만 하면 됩니다. 그러면 해당 PID를 갖는 서버 프로세스에 메시지가 날아가죠.

그런데 이 함수에는 좀 미심쩍은 구석이 있습니다. 메시지로 받은 L이 리스트가 아니면 어떻게 되는거죠?

그런 미심쩍음을 좀 해소하기 위해, 이번에는 리스트와 함께 아톰으로 된 헤더를 붙여 전달해야 처리되도록 코드를 바꿔 보죠. loop()의 코드만 다음과 같이 바꿔보겠습니다.

loop() ->
    receive
        {list, L} ->
            {{odd, Ov}, {even, Ev}} = odds_and_evens_acc(L),
            io:format("~p, ~p~n", [Ov, Ev]),
            loop();
        stop ->
            exit
    end.

그렇게 한 다음에 다음과 같이 실행해 보죠.

27> f().
ok
28> c(concur).
{ok,concur}
29> Q = concur:start().
<0.104.0>
30> Q ! [1,2,3].
[1,2,3]
31> Q ! {list, [1,2,3]}.
{list,[1,2,3]}
4, 2
32> Q ! stop.
stop
33> Q ! {list, [1,2,3]}.
{list,[1,2,3]}
34>

이제 서버의 함수가 정상적으로 실행되도록 하려면, 메시지 앞에 list라는 아톰을 붙여야 합니다. 다른 형태의 메시지를 보내면 (list가 아닌 다른 아톰을 헤더로 사용하거나 아예 형식이 다른 메시지를 보내면) 그 메시지는 아예 loop 함수에 의해 처리가 되지 않는다는 것도 볼 수 있습니다. (위의 30>번 실행 결과를 보세요.) 서버의 동작에 영향을 미치지 못하는 거죠. (이 점이 Erlang 서버의 구현을 좀 더 편하게 만든다고도 볼 수 있습니다. 명시적으로 무시하는 로직을 작성할 수도 있습니다만, 그렇게 하지 않아도 어쨌든 되긴 된다는 거죠. )

서버의 동작을 중지시키려면 stop이라는 메시지를 보내면 되고, 그런 다음에는 concur:start()를 호출한 결과로 받은 PID에 대고 메시지를 전송시켜 봐야 아무일도 하지 않습니다. 서버의 loop() 함수 수행이 이미 끝났거든요. (32>와 32>을 보세요.) 어쨌거나, 위의 코드에는 아직도 L이 리스트인지 아닌지를 검사하는 부분은 없습니다. 다음과 같이 한번 바꿔보죠.

loop() ->
    receive
        {list, L} ->
            case is_list(L) of
                true ->
                    {{odd, Ov}, {even, Ev}} = odds_and_evens_acc(L),
                    io:format("~p, ~p~n", [Ov, Ev]),
                    loop();
                false ->
                    io:format("second argument is not a list"),
                    loop()
            end;
        stop ->
            exit
    end.

이렇게 한 다음에 다음과 같이 실행해 보겠습니다.

3> c(concur).
{ok,concur}
4> Pid = concur:start().
<0.46.0>
5> Pid ! { list, [1,2,3,4,5] }.
{list,[1,2,3,4,5]}
9, 6
6> Pid ! { list, 3 }.
{list,3}
second argument is not a list
7>

어떤 멍청한 인간이 리스트가 아닌 다른 무엇을 보내도 사고가 생기지 않는 코드를 만들고자 이렇게 했습니다. 종전 코드에서는, 클라이언트가 만일 리스트가 아닌 다른 무엇을 서버에 보냈더라면 실행 시간에 오류가 발생했을 겁니다. 이처럼, 얼랭으로 프로그램을 짤 때 문제가 될 수 있는 부분은, 대응규칙(matching rule)으로 처리될 수 없는 비정상적인 어떤 일이 벌어졌을 때가 대부분입니다. 그런 부분을 막도록 프로그램을 잘 짜야 괴상망칙한 일이 벌어지는 것을 미연에 방지할 수 있죠.

자. 그렇다면 이제 list라는 아톰 헤더는 필요가 없는거군요? 그렇습니다. list라는 아톰 대신, 뭔가 다른 그럴싸한 아톰이 오면 좋을 것 같습니다. 어쩄든 '이 메시지가 무슨 메시지인지를' 표현하는 헤더는 필요한 것이니까요. 다음 글에서 예제를 그에 맞게 조금 수정하도록 하겠습니다. 그리고 지금까지의 예제는 클라이언트가 메시지를 보내면 서버가 그 실행 결과를 화면에 뿌리는 것이었습니다만, 다음 글에서는 이제 진짜 클라이언트-서버 모델에 맞게 프로그램을 더 고쳐보겠습니다. 그러려면 고려해야 할 것들이 좀 더 많습니다.



신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Erlang2008.09.08 20:05

Erlang은 함수형 언어이고, 모든 프로그래밍 행위가 함수를 정의하는 것에서부터 출발합니다. Erlang 함수에는 두 가지 종류가 있습니다.

1. 이름 있는 함수
2. 이름 없는 함수

이름 있는 함수의 예제는 지금까지 보셨으니 잘 아실테구요. (이름 있는 함수들은 C나 Java에서도 흔한 것이니까 이해하기도 쉬운 편이죠.) 이번 글에서는 이름 없는 함수를 살펴보겠습니다.

이름 없는 함수의 사례는 JavaScript 같은 프로그래밍 언어에서 쉽게 찾아볼 수 있습니다. 제가 예전에 JavaScript 관련 글을 적으면서 JavaScript에서는 함수도 객체다라는 이야기를 한 적이 있었는데요. 다음 예제를 한 번 보시죠.

function addGenerator( num ) {
    return function( toAdd ) {
       return num + toAdd;
   }
}

var addFive = addGenerator( 5 );

alert( addFive(4) == 9 );

위의 예제를 보면 함수가 함수를 반환하고 있고, 반환된 함수를 다시 호출하고 있습니다. Erlang에서도 당연히 이런 프로그래밍이 가능합니다. 가령 위와 똑같은 코드를 Erlang으로 작성해 본다면...

4> AddGenerator = fun(Num) -> ( fun(X) -> Num + X end ) end.
5> AddFive = AddGenerator(5).
6> AddFive(4).
9.

뭐 이렇게 되는 겁니다. 여기서 중요하게 보셔야 할 부분이 바로 fun()의 쓰임새인데요. JavaScript에서 function()이 익명의 함수 객체를 만들어 반환하는 데 쓰이는 것 처럼, fun()도 Erlang에서 같은 쓰임새를 가지고 있습니다.

Erlang에서 fun 정의는 fun 키워드로 시작해서 end로 끝납니다. 그러니까 이런 식이죠.

1> Mult = fun(X, Y) -> X * Y end.
2> Mult(3, 4)
12

정의한 fun을 변수에 대응시켜 변수 이름을 통해 호출하고 있습니다. fun도 함수인지라 일반 Erlang 함수와 같이 절을 가질 수 있습니다. 근데 fun에는 함수 이름이 없기 떄문에, 그 문법은 일반 함수와 조금 틀립니다. 가령, 다음의 fun을 한번 보시죠.

1> Max = fun(X, Y) when X>Y -> X; (X, Y) -> Y end.
#Fun<erl_eval.12.102015280>
2> Max(2,3).
3

이전 글에서 배웠던 Max 함수의 정의외 바교해 보면, 차이가 분명합니다. 적어줄 함수 이름이 없기 때문에, 괄호와 인자 리스트만 절 앞부분에 적어주는 것이죠.

이렇게 해서 만들어진 fun은 함수의 인자로 넘길 수 있습니다. 물론 이름 있는 함수들도 다른 함수의 인자로 전달할 수 있습니다. 하지만 fun을 사용하면 좀 더 재미있게(fun!) 코드를 작성할 수 있습니다. 예를 한번 살펴보죠. Erlang의 표준 라이브러리 lists라는 모듈에는 map이라는 함수가 정의되어 있습니다. 이 함수는 인자를 두 개 받는데요. 첫 번째 인자는 함수이고, 두 번째 인자는 리스트입니다. map은 두 번째 인자에 보관된 각각의 원소에 첫 번째 인자로 주어진 함수를 적용한 결과를 리스트 형태로 반환합니다. 다음의 코드를 보시죠.

5> Double = fun(X) -> 2 * X end.
#Fun<erl_eval.6.49591080>
6> L = [1, 2, 3, 4, 5].
[1,2,3,4,5]
7> lists:map(Double, L).
[2,4,6,8,10]

예상했던 결과죠? 그런데 저렇게 코드를 작성하는 대신 다음과 같이 해도 됩니다.

8> lists:map(fun(X) -> 2*X end, L).
[2,4,6,8,10]
9>

jQuery 써서 자바스크립트 프로그래밍 많이들 하시는 분은 아마 다음과 같은 형식에 꽤나 익숙하실텐데, 비슷한 형태의 문법이라고 할 수 있죠.

$(function() {
   ...
});

자. 지금까지 Erlang 함수가 fun이나 함수를 인자로 받을 수 있음을 살펴봤습니다. 뿐만 아니라, Erlang 함수는 fun을 반환(return)할 수도 있습니다. 이에 대한 예제는 맨 위의 AddGenerator 예제를 통해 살펴봤었죠. 그런데 Erlang 함수에서 다른 이름 붙은 Erlang 함수를 반환할 수는 있을까요?

됩니다. (첨엔 안된다고 썼었는데 죄송.)

다음과 같이 하면 됩니다.

-module(test)
-export([get_sum/0]).

sum(L) -> sum(L, 0).

sum([H|T], S) -> sum(T, H + S);
sum([], S) -> S.

get_sum() -> fun sum/1.

위와 같이 코드를 작성하셨으면 컴파일 해서 실행을 한 번 해 보죠.

1> c(test).
{ok,test}
2> X = test:get_sum().
#Fun<test.0.55022140>
3> X([1,2,3,4,5,6]).
21
4>

어쨌던 fun이라는 키워드를 사용하는 것이 요령입니다. /1과 같이 arity를 명시해 주는 부분도 잊으면 안되겠죠.

신고
Posted by 이병준
TAG Erlang, fun, 함수

소중한 의견, 감사합니다. ^^

Languages/Erlang2008.09.05 14:11

C같은 프로그래밍 언어에는 순환문 지원을 위한 다양한 문법들을 제공하고 있습니다. for, while, do-while 등등이 그것이죠. 물론 순환문을 위한 '특별한' 문법을 사용하지 않고도 프로그래밍을 할 수 있긴 합니다. 가령 C의 다음과 같은 프로그램을 한번 생각해 보죠.

int sum(int[] array, size_t n) {
    int sum = 0;
    for ( int i = 0; i < n; ++i ) {
        sum += array[i];
    }
}

for를 사용해 배열에 있는 모든 값들을 합산하고 있습니다. 그런데 for라는 키워드를 사용할 수가 없고, 그냥 함수 개념만 사용해 프로그래밍을 해야 한다면, 문제를 어떻게 해결할 수 있을까요?

int sum(int[] array, size_t n) {
    return sum_array(array, n, 0, 0);
}

int sum_array(int[] array, size_t n, int start_index, int sum) {
    if ( n == 0 ) {
        return sum;
    }

   return sum_array(array, n-1, start_index + 1, sum + array[start_index]);
}

뭐 이렇게도 할 수 있지 않았을까요? 여기서 주목해 볼 것은, 인자 sum을 통해 최종적으로 반환될 합계 값을 추적한다는 점입니다. 이런 프로시저는 "컴퓨터 프로그램의 구조와 해석"이라는 책에서 쓰인 용어를 빌려 말하자면, "되도는 프로시저"이고 "반복적인 프로세스"이죠. (이에 대해서는 해당 책을 참조하시길. ㅋㅋ) 반복적인 프로세스는 재귀적인 프로세스와는 달리 유한한 메모리를 사용해 문제를 풀 수 있습니다. (재귀적인 프로세스는 그 계산과정을 추적하기 위해 재귀 호출이 뻗어나간 각각의 가지에 대한 정보를 계산이 끝나서 그 결과가 '말아올려질 때 까지' 들고 있어야 합니다.)

하지만 C나 Java같은 프로그래밍 언어는 설사 프로세스가 풀려나가는 과정이 반복적(iterative)이라고 하더라도 일단 재귀적인 프로시저를 통해 구현했다면 전부 똑같이 처리합니다. 가령, 위의 sum_array 함수는 네 개의 변수만 사용하면 그 계산과정 전부를 온전히 추적할 수 있음에도, C 컴파일러는 냉정하게 함수가 새로 호출될 때 마다 그에 필요한 스택을 강제로 할당해 버립니다. 결국 배열이 길어지면 stack overflow가 나고 말죠. 그래서 for나 whie, do-while 같은 별도의 문법이 존재하는 겁니다.

하지만 Erlang이나 Scheme같은 프로그래밍 언어는 순환문을 위한 별도의 문법을 제공하지 않습니다. Erlang에서 위의 문제를 어떻게 푸는지 한번 살펴보도록 하죠. 재귀적 프로시저를 사용해 코드를 작성해야 합니다.

sum([H|T]) -> H + sum(T);
sum([]) -> 0.

위의 sum/1 함수는 리스트 하나를 인자로 받습니다. 리스트가 비어 있지 ([]) 않을 경우, 대응 규칙에 따라 이 리스트는 위의 첫 번째 절로 전달됩니다. 첫 번째 절에서 인자는 머리 (H)와 나머지(T) 부분으로 분리됩니다. 그러므로 sum이 반환해야 할 값은 H와 sum(T)의 합이 됩니다. 간단하죠?

그런데 위의 함수는 되도는 프로세스 이지 반복적인 프로세스는 아닙니다. 그다지 효율적으로 메모리를 사용하지 못한다는 뜻이 되겠군요. 이 프로세스를 반복적 프로세스로 바꿀려면, 다음과 같이 해야 할겁니다.

sum(L) -> sum(L, 0).

sum([H|T], S) -> sum(T, H + S);
sum([], S) -> S.

이렇게 하고 Erlang 셸에서 다음과 같이 해 보면 결과가 제대로 출력되는 것을 볼 수 있습니다.

14> L = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
[1,2,3,4,5,6,7,8,9,10]
15> c(test).
...
20> test:sum(L).
55
21>

결국 Erlang에서 순환문은 함수를 재귀적으로 작성하여 만들게 된다는 이야기입니다. 간단한 이야기를 하려던 것이었는데 말이 너무 길어졌군요 ㅜㅜ 어쨌건 순환문을 만드는 데 있어서 재귀적 프로세스와 반복적 프로세스에 대한 차이는 반드시 알아두는 것이 좋습니다. Erlang에서는 그 차이를 정확하게 이해해야 효율적인 프로그램을 짤 수 있을 것이거든요.

"컴퓨터 프로그램의 구조와 해석"이 좀 비싸긴 합니다만 한권 장만해두시는 것도 좋겠습니다. ㅋㅋㅋㅋㅋㅋ

그럼 보는 김에 "프로그래밍 얼랭"에 나오는 예를 하나 더 살펴볼까요? 완전히 같은건 아니고 제가 좀 바꿨습니다.

odds_and_evens_acc(L) ->
    odds_and_evens_acc(L, 0, 0).

odds_and_evens_acc([H|T], Odds, Evens) ->
    case ( H rem 2 ) of
        1 -> odds_and_evens_acc(T, Odds + H, Evens);
        0 -> odds_and_evens_acc(T, Odds, Evens + H)
    end;
odds_and_evens_acc([], Odds, Evens) ->
    { {odd, Odds}, {even, Evens} }.


29> c(test).
{ok,test}
30> test:odds_and_evens_acc(L).
{{odd,25},{even,30}}
31>

홀수끼리의 합, 짝수끼리의 합을 구해서 투플 형태로 반환하는 예입니다. case 문도 사용했고, 투플에 아톰을 넣어뒀기 때문에 반환값의 의미를 좀 명확하게 볼 수 있습니다.


신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Erlang2008.09.04 11:00

C를 배울때 변수에 대해서 배우고 난 다음에, 각종 제어문에 대해서 공부했던 기억이 납니다. 제가 어렸을 적에 영어를 처음 공부할 때 쉬운 단어들부터 하나둘씩 배우고 그 다음에 문장을 어떻게 만드는지를 공부했던 것같은데, 프로그래밍 언어를 공부하는 데도 비슷한 구석이 있는 것 같군요.

암튼 오늘은 Erlang에서의 제어문에 대해서 좀 살펴보도록 하겠습니다. C 프로그래머에게 있어 가장 익숙한 제어문이라면 if-else 같은 것이 있겠고, 드물게 쓰이지만 goto 같은 것도 있겠군요. 제어(control)라는 말이 의미하듯, 제어문은 프로그램의 흐름을 조정하기 위해 사용되는 구문입니다.

그런데 Erlang은 함수형 언어라, 이 제어문이라는 것이 C와 같은 절차형 프로그래밍 언어와는 좀 다른 양상으로 나타나는 경향이 있습니다.

그 이야기를 하기 전에, 우선 Erlang 프로그램 파일이 어떻게 구성되는지부터 한번 살펴보도록 하겠습니다. 앞서 살펴보았던 예제들은 전부 Erlang 셸에서 실행되는 예제들이었는데요. 오늘부터는 완전한 프로그램 파일을 두고 예제를 살펴보려고 합시다. 그러니 Erlang 프로그램의 구조에 대해 알아두는 것이 좋겠죠.

다음은 Erlang 프로그램 파일의 가장 기본적인 형태입니다.

-module(foo).
-export([bar/1, bar/2]).

...

-module은 Erlang 모듈을 정의하기 위한 부분입니다. Erlang 프로그램 파일의 이름이 foo.erl 이라면 (Erlang 프로그램 파일은 erl의 확장자를 갖습니다) 모듈의 이름은 foo 라고 짓습니다. 모듈의 이름은 그 안에 담긴 함수들이 어떤 일을 하는지를 잘 나타내도록 지어야 합니다.

-export는 해당 모듈 안에 정의된 함수 중, 모듈 외부에서 호출하여 쓰일 수 있는 함수의 이름과 그 인자의 개수를 명시하기 위해 사용합니다. 여기에 적히지 않은 함수들은 외부에서 호출될 수 없습니다. 함수들의 이름은 ,로 구분하여 Erlang 리스트 형식으로 나열합니다. 간단하죠?

그 아래쪽에는 이제 Erlang 함수들이 오게 됩니다.

Erlang 이라는 프로그래밍 언어가 함수형 언어이다보니, Erlang에서는 프로그램 코드가 전부 함수를 통해 작성됩니다. C에서는 함수의 시그너춰(signature)를 명시할 때 함수의 반환값 타입, 이름(name), 그리고 인자 리스트 순서대로 적었었죠. 하지만 Erlang에서는 함수의 이름과 인자 리스트만 명시하면 됩니다. 반환값은? 어떤 것이라도 반환할 수 있습니다. -_-; (이 점이 가끔 strong-typed 언어에 익숙한 프로그래머들을 당황하게 하긴 합니다.ㅋㅋ)

그럼 우선 간단한 예제를 보도록 하죠.

-module(geeometry).
-export([area/1]).

area({rectangle, Width, Ht}) -> Width * Ht;
area({circle, R}) -> 3.14159 * R * R.

위의 Erlang 모듈 geometry에는 인자를 하나만 받는 area라는 함수가 정의되어 있습니다. 인자를 받아, 그 인자가 나타내는 도형의 면적을 계산해 주는 함수입니다. C라면 이런 코드를 어떻게 작성했을까요? 코드의 효율성이나 가독성같은 것은 싹 다 무시하고, 가장 간단하게 한번 만들어 봅시다.

double area(int type, double arg1, double arg2) {
    if ( type == 1 ) { // rectangle
        return arg1 * arg2;
    }
    else if ( type == 2 ) { // circle
        return 3.14159 * arg1 * arg1;
    }

    return 0.0;
}

어떤 도형이냐를 판단하기 위해 type을 두었고, 면적을 계산하는 데 쓰일 인자들을 받기 위해 arg1과 arg2를 두었습니다. type의 값이 무엇이냐에 따라 도형의 면적을 계산하는 공식은 달리 적용하고 있습니다.

이 코드의 문제점은, 코드가 의미하는 바를 이해하기 힘들다는 것이고 (의도적으로 가독성을 무시했으니 당연한가요? ㅋㅋ) 한 함수 안에 여러 가지 기능들이 뭉개져 있다는 것입니다. 함수가 기능을 표상하는 것이 아니라, 함수 내의 if-else 블럭들이 기능을 표상하게 됩니다. 그 문제를 해결하기 위해 area_rectangle과 area_circle같은 함수들을 계속 만들게 되면 기능 분리는 제대로 될지 모르지만 함수 이름 오버로딩이 지원되지 않는다는 문제때문에 프로그램 코드는 영 보기 싫어지게 되죠.

말이 좀 길었습니다만, Erlang은 이런 문제들을 꽤 우아하게 피해갑니다. 앞선 글에서도 언급한 바 있는, 변수에 대한 대응 규칙 덕분이죠.

Erlang에서는 함수에 인자를 전달할 때, 그 인자에 최적으로 대응되는 대응 규칙을 가지고 있는 함수에 전달합니다. 가령 {rectangle, 3, 4}와 같은 투플을 위의 모듈이 export하고 있는 area 함수에 전달한다고 해 보죠. 이 인자가 대응될 수 있는 대응 규칙은 {rectangle, Width, Ht} 쪽입니다. 그러니 결국 {rectangle, Width, Ht} = {rectangle, 3, 4}의 대응 연산이 실행되면서 Width와 Ht에는 각각 3과 4가 바인딩 되고, 이 값을 사용해 사각형(인자로 전달된)의 면적을 계산하게 됩니다.

한번 실행해 볼까요?

먼저 프로그램 파일이 있는 디렉터리에서 Erlang 셸을 열고, 다음과 같이 합니다.

1> c(geometry).
{ok, geometry}
2> geometry:area({rectangle, 3, 4}).
12

circle에 대해서도 마찬가지로 할 수 있겠군요.

위의 예제가 시사하는 바는 이런 겁니다. Erlang에서는 함수를 기능 단위로 분할하여 작성하는 것으로 기존 프로그래밍 언어의 제어문이 하던 일을 대체할 수 있다는 것이죠.

물론 그렇다고 Erlang에 If 문이 없는 건 아닙니다. Erlang에도 If문은 있죠. 다만 함수를 통해 기존의 제어문이 어떻게 우아한 형태로 바뀔 수 있는지를 보이기 위해 좀 장황하게 설명을 했을 뿐입니다. 아. 그런데 깜빡하게 말씀 안드린 부분이 있군요.

위에서처럼 하나의 함수를 여러 개의 함수들로 쪼개어 작성할 때, 결국 그것이 하나의 함수이기 때문에, 각각의 부분함수들은 ; 로 구분합니다. 제일 마지막에만 . 로 끝맺음을 하게 되죠. 결국 하나의 함수를 여러 개의 절로 분할하여 작성하는 거라고 이해하시면 되겠습니다. ;는 절을 구분하는 구분자이고, . 는 함수의 마지막을 표시하는 구둣점이라고 생각하시면 되겠네요.

그런데 여기까지 설명드리면 아마 이런 의문이 생기실수도 있겠어요.

"패턴 대응 규칙으로 처리할 수 없는 조건은 어떻게 함수 절로 분할할수 있는가?"

가령 이런 겁니다. max(X, Y)를 Erlang 함수로 구현한다고 해 봅시다. 이 경우에는 단순히 패턴 대응 규칙을 잘 작성하는 것 만으로는 하나의 함수를 절 단위로 분할할 수 없습니다. 그러니 이런 경우에는 가드(guard)라는 것을 사용해야 합니다. 다음의 예제 코드를 보시죠. 붉은 색으로 표시된 부분이 가드입니다. 가드는 when 키워드를 사용해 명시합니다.

max(X, Y) when X > Y -> X;
max(X, Y) -> Y.

X가 Y보다 클 때는 X를 반환하고, 그렇지 않을때는 Y를 반환하는 코드입니다. 주어진 인자를 처리할 수 있는 함수 절을 찾을 때 위에서 아래쪽으로 찾기 떄문에, 아래쪽에는 가드를 적어주지 않았습니다.

하나 이상의 가드를 연결할 떄에는 ,를 사용해 연결할 수도 있고, ;를 사용해 연결할 수도 있습니다. ,를 사용하면 가드 식은 논리적으로 보아 AND로 연결된 것이고, ;를 사용하면 OR로 연결된 것입니다. 더 자세한 내용은 Erlang 교과서 ("프로그래밍 얼랭")를 참조하시기 바랍니다. 시시콜콜한 문법적인 디테일까지를 전부 다 설명하는 것은 피하도록 하겠습니다. :-P

각설하고, 지금까지의 예제는 함수의 '절'을 사용해서 C같은 프로그래밍 언어의 제어문이 하는 일을 흉내내는 것이었습니다. 그런데 Erlang에 제어문을 위한 구문이 없느냐  하면 그건 또 아니거든요. Erlang도 if나 case 등을 지원합니다. 물론, 쓰는 방법은 좀 다릅니다. 예를 들어 설명해 보죠. 위의 max(X, Y) 함수를 case 문을 사용해 바꿔 보면 다음과 같습니다.

max(X, Y) ->
    case (X>Y) of
        true -> X;
        false -> Y
    end.

우선 (X>Y)의 값을 계산한 다음에 (C의 switch-case 문과 하는 일이 똑같습니다) 그 값이 대응되는 규칙을 case 문 안의 각각의 절 왼쪽에서 찾아서, 발견되면 그 오른쪽에 오는 식을 계산해 값을 반환하는 형태입니다.

if 문으로 위의 코드를 바꿔 쓰면 다음과 같습니다.

max(X, Y) ->
    if
        (X>Y) -> X;
        true -> Y
    end.

if 문 안에 오는 각각의 절들은 위에서 아래로 검토됩니다. 검토될 때 마다 ->의 좌측에 오는 가드가 계산되고, 그 값이 참이면 우측에 오는 식의 값이 계산되어 반환됩니다. 그런데 Erlang의 if는 C의 if와는 조금 다른 것이, C에서의 if는 else가 없어도 괜찮았지만 Erlang에서의 if는 그렇지 않다는 말입니다. 간단하게 말하면, Erlang의 if는 반드시 어떤 값을 반환해야 합니다. 그렇기 때문에 하나의 가드는 반드시 참으로 계산되어야 하고, 그 결과로 값을 반환해야 합니다. 위의 if 절 마지막에 true라는 가드를 둔 것은 그때문이죠. (C의 else에 해당합니다.) 그렇지 않으면 실행 도중에 예외가 발생합니다. if의 값을 계산할 수 없다고 투덜대면서 말이에요.

max(X, Y) ->
    Z = if
            (X>Y) -> X;
            true -> Y
        end,
    Z.

If가 값을 반환한다는 것이 놀랍게 느껴지는 분을 위해 예를 하나 써 봤습니다. 물론 이렇게 프로그램을 짤 일은 없겠지만 말이에요. 그럼 case는 어떤가요? case도 마찬가집니다.

max(X, Y) ->
    Z = case (X>Y) of
            true -> X;
            false -> Y
        end,
    Z.

그럼 case도 마치 else 있는 if 문처럼 동작해야 하겠군요? 맞습니다. :-)

제어문에 대한 글은 여기서 맺겠습니다. 중간에 이런 저런 업데이트를 많이 해서 '혹시나' 이 글을 읽으셨을 분들은 좀 헷갈리셨을 것 같군요. 죄송합니다.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Erlang2008.09.01 17:07
앞선 글에서 Erlang의 타입 시스템이 C와 같은 프로그래밍 언어와 어떻게 다른지에 대한 이야기를 잠깐 했습니다. 아톰, 리스트, 투플 같은 낯선 언어 구조물들에 대한 언급도 잠깐 했었구요.

이번 글에서는 그 '낯선 구조물들'에 대한 설명을 조금 해 보려고 합니다. 먼저 아톰입니다.

Erlang에서 아톰은 소문자로 시작하는 이름입니다. 이름이라고는 하지만 그에 대응하는 값을 갖는 것은 아니고, 그 자체로 의미가 있습니다. Ruby라는 프로그래밍 언어에 익숙한 분이라면 아마 심볼(symbol)이라는 개념을 아실텐데요. Ruby에서 심볼은 다음과 같은 형태를 가집니다.

:foo

심볼은 그 자체로 유일합니다. Erlang의 아톰도 마찬가지입니다. C의 매크로 상수같은 것과 비슷한 개념이 아니냐고 물을 분도 계실텐데, #define을 사용해 정의한 매크로 상수들은 '값으로 치환되는 이름'이고 그 이름 자체가 비교의 대상이 될 수는 없기 때문에 같은 개념이라고 볼 수는 없습니다. 이해가 잘 되지 않는다면, 우선은 그냥 '사용자 정의 상수'쯤으로 생각해 버립시다. 그 이름이 곧 그 값인 상수 말이죠.

일례로, Erlang 셸에서 다음과 같이 아톰을 입력해 보면, 아톰 그 자체가 값으로 반환되는 것을 볼 수 있습니다. 별도의 값으로 치환되는 이름이 아니라는 뜻입니다. 아톰이 유용하게 사용되는 사례는 잠시 후 투플에 대해 설명하면서 살펴보도록 하겠습니다.

1> foo.
foo

투플은 C라면 구조체와 비슷한 개념인데, {와 }를 둘러싸 만듭니다. 가령 다음과 같은 C 구조체가 있다고 합시다.

struct Entry {
    int code;
    int value;
};

C에서 이 구조체로 변수를 만들고 그 값을 조작하려면 다음과 같이 하게 될 것입니다.

Entry a;
a.code = 3;
a.value = 12;

앞서도 말했지만 Erlang에는 '정적으로 변수에 타입을 준다'는 개념이 없기 때문에, 그냥 다음과 같이 합니다.

1> P = { 3, 12 }.

그런데 이렇게 하면 이 투플이 대체 무슨 투플인지 알 길이 전혀 없다는 문제가 있습니다. 그러므로 보통은 다음과 같이 하는 쪽을 선호합니다.

1> P = { entry, {code, 3}, {value, 12} }.

아톰을 통해 각각의 정보 단위에 이름을 주었다는 점을 유의해서 봅시다. 이렇게 하면 뭔가 잘 정의된 의미 구조를 갖는 투플을 만들고 사용할 수 있습니다. 그런데 대체 저 투플 안에 저장된 값을 뽑아낼때는 대체 어떻게 해야 하나요? -_-;;

앞선 글에서 = 연산자가 사실은 '대응 연산자'라고 말씀드렸던 것을 상기합시다. 값을 뽑아내려면, 이 연산자를 가지고 대응 규칙을 정의하면 됩니다. 다음을 보시죠. 붉은 색으로 표시한 부분은 아직 사용된 적이 없는 변수들이라는 점을 유의해 보십시다.

2> {entry, CodeTuple, ValueTuple} = P.
{entry,{code,3},{value,12}}
3> CodeTuple.
{code,3}
4> ValueTuple.
{value,12}

대응 연산자를 사용해 대응 규칙을 정의하고 있습니다. 대응 규칙이라는 좀 무시무시한 용어를 쓰긴 했습니다만, 사실 대층 규칙이라는 것은 별게 아닙니다. = 연산자 오른쪽에 오는 투플의 각 요소가 = 연산자 왼쪽에 오는 투플의 각 요소들에 어떻게 대응되는지를 명시하는 것에 불과합니다. 그 결과, CodeTuple은 {code, 3}에 대응되었고, ValueTuple은 {value, 12}에 대응되었습니다. 이런 식으로 하면 복잡한 투플 내의 임의 위치에 있는 값을 알아낼 수 있게 됩니다. 이 규칙을 조금 응용하면, 다음과 같이 해도 되겠군요.

5> {entry, {code, X}, {value, Y}} = P.
{entry,{code,3},{value,12}}
6> X.
3
7> Y.
12

이 예제들을 통해 아톰, 투플, 그리고 변수의 사용에 대해 약간의 감을 잡으셨을 수 있으리라 생각됩니다.

말씀드리는 김에 조금 더 나가볼까요? 대응 관계가 만들어진 변수를 통상 바운드(bound)된 변수라고 부릅니다. 그럼 언바운드 변수라는 것도 있나요? 네. 있긴 합니다. 언바운드 변수는 대응 규칙을 좀 더 느슨하게 만들고자 할 때 사용합니다. 다음 예제를 보시죠.

8> {_, {_, A}, {_, B}} = P.
{entry,{code,3},{value,12}}
9> A.
3
10> B.
12

언바운드 변수 '_'를 사용해서 대응 규칙을 만들면 투플의 구조만 일치하면 그 특정 위치에 오는 값을 뽑아낼 수 있습니다. (뽑아낼 수 있는 값에는 제한이 없습니다. 아톰이 될 수도 있고, 투플이 될 수도 있고, 리스트가 될 수도 있겠죠.)

그럼 이제 마지막으로 리스트에 대해서 말씀드리고 이번 글은 맺겠습니다. 리스트는 '['와 ',' 그리고 ']'를 가지고 만드는, 일련의 값의 묶음입니다. 묶이는 대상은 일반 값이 될 수도 있고, 투플이나 아톰이 될 수도 있고, 또다른 리스트가 될 수도 있습니다. 하나의 리스트 안에는 서로 다른 타입의 값들이 어떤 순서로도 저장될 수 있습니다.

11> L = [2, CodeTuple, ValueTuple, X, Y].
[2,{code,3},{value,12},3,12]

가령 지금까지의 예제들을 충실하게 실행한 상태에서 위와 같이 하면, 보시다시피 [2,{code,3},{value,12},3,12]의 리스트가 만들어지게 되죠.

[다음 글에서 계속...]




신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. <A href="http://blueandyellow.blog22.fc2.com/">SEX</A>できるサイト<A href="http://pyridoxiamine.blog34.fc2.com/">逆援助</A>専門の出会い<A href="http://gyakuenjyoyattemita.blog10.fc2.com/">逆援助</A>でセレブ女性とH<A href="http://douteiget.blog102.fc2.com/">童貞</A>喪失も可能<A href="http://hurinxxx.blog19.fc2.com/">不倫</A>

    2009.02.28 10:46 신고 [ ADDR : EDIT/ DEL : REPLY ]

Languages/Erlang2008.07.04 12:28

yaws를 설치했다면 Erlyweb도 설치해보고 싶은 것이 인지상정일 터인데, 설치 방법이 간단하긴 하지만 그렇다고 한방에 잘 되지도 않는다. 다음과 같이 하면 큰 무리 없이 잘 될 것.

BASIC TROUBLESHOOTING : 설치시 생기는 문제들

우선, http://erlyweb.org/ 에 가서 최신 버전의 erlyweb distribution을 받는다. 그런 다음에 erlang의 lib 디렉터리 아래에다 이 파일의 압축을 푼다. 필자의 경우, /usr/lib/erlang/lib 아래에 풀어주었다. erlang library가 어디에 설치되어 있는지 모르겠다면, erl 프롬프트상에서 code:lib_dir(). 를 입력하고 리턴 키를 눌러주면 된다.

원래는 (?) 압축을 풀어주는 것 만으로 모든 것이 해결되어야 한다. 제대로 되었다면, 이제 작업 디렉터리로 가서 erl 셸을 실행한 다음에 다음과 같이 해 본다.

1> erlyweb:create_app("testapp", ".").


이렇게 하면 다음과 같은 메시지가 나오면서 해당 디렉터리 아래에 src,www, ebin 등의 디렉터리가 생성되어야 한다.

1> erlyweb:create_app("testapp", ".").
info:erlyweb_util:39: creating "./testapp"
info:erlyweb_util:39: creating "./testapp/src"
info:erlyweb_util:39: creating "./testapp/src/components"
info:erlyweb_util:39: creating "./testapp/www"
info:erlyweb_util:39: creating "./testapp/ebin"
info:erlyweb_util:70: creating "./testapp/src/components/html_container_view.et"
info:erlyweb_util:70: creating "./testapp/src/components/html_container_controller.erl"
info:erlyweb_util:70: creating "./testapp/src/mh20_app_controller.erl"
info:erlyweb_util:70: creating "./testapp/www/index.html"
info:erlyweb_util:70: creating "./testapp/www/style.css"
ok


그런데 잘 되지 않고, 간혹(?)

=ERROR REPORT==== 17-May-2007::11:43:31 ===
beam/beam_load.c(1097): Error loading module erlyweb:
  use of opcode 136; this emulator supports only up to 129


위와 유사한 오류 메시자가 뜨는 경우가 있다. 그럴 때는 erlyweb 빌드를 다시 해 주어야 한다. /usr/lib/erlang/lib/erlyweb-1.76 (필자의 경우) 로 cd 한 다음에, make clean ; make를 때려주자. 십중팔구는 그것도 잘 안될 것이다. 오류 메시지를 유심히 보면, yaws_api.hrl 파일을 찾지 못하는 것이 원인임을 알 수 있다.

그 때는 cd src/erlyweb을 일단 하자. 위의 문제를 해결하는 데는 여러 가지 우아한 다른 방법이 있을 수 있겠지만, 성질급한 사람에게 추천하는 가장 빠른 방법은, 이 디렉터리 안에서

ln -s <yaws 헤더 파일이 있는 디렉터리>/yaws_api.hrl .


를 해 주는 것이다. 필자의 경우 저 디렉터리는 yaws를 local home directory에 설치했기 때문에 /home/bjlee/packages/yaws-1.76/include 였다.

이렇게 한 다음에 다시 cd ../.. 해서 /usr/lib/erlang/lib/erlyweb-1.76 (필자의 경우) 로 간 다음에 make를 때려보면 정상적으로 컴파일이 될 것이다.

그 다음으로 남은 일은 http://www.ibm.com/developerworks/kr/library/opendw/20080617/ 여기를 참고하면 될 것이다. :-)

MORE TROUBLESHOOTING : 설치 이후

1. 컴파일 문제

그런데 만일 yaws를 띄우는건 잘 되는데 erlyweb app가 컴파일이 잘 안된다면? 특히 다음과 같은 오류 메시지를 내는 경우에는 어떻게 해야 좋을까?

3> erlyweb:compile("testapp", [{erlydb_driver, mysql}, {auto_compile, true}]).
debug:erlyweb_compile:379: Compiling Erlang file "testapp_app_controller"
error:erlyweb_compile:89: Error compiling app controller
** exited: {error,{invalid_module,"testapp/src/testapp_app_controller.erl"}} **

그럴 경우에는 yaws의 interactive command prompt 상에서 pwd().를 해 본다. 아마 testapp를 생성한 디렉터리가 아니게 되어 있을 것이다. 그럴 경우에는

4> cd("/home/bjlee/work/yaws/erlyweb/").


와 같이 해 주면 된다. 필자의 경우에는 저 디렉터리 아래에 testapp 디렉터리가 있었다.

2. MySQL 문제

erlyweb:compile을 처음으로 실행할때는 MySQL을 쓰던 mnesia를 쓰던 사실 별 상관은 없지만, 본격적으로 모델을 만들고 데이터베이스를 붙이려고 하다 보면 이제 문제가 조금씩 발생한다. 가장 크게 문제가 되는 것은 erlyweb:compile이 정상적으로 실행되려면 시스템에 MySQL이 깔려 있어야 한다는 것. (mnesia를 쓰겠다면 아무 문제 없겠지만...)

MySQL을 깔고 나서 yaws interactive shell에서 다음과 같이 해 보자. 이렇게 해 두어야 erlyweb:compile 과정에서 발생하는 문제가 사라진다. 아래의 과정은 mysql server에의 접속 정보를 확인하는 과정과 같은 것이라고 보아도 일단은 무방하다.

erlydb:start(mysql, [{hostname, "localhost"}, {username, "xxxx"}, {password, "yyyy"}, {database, "testdb"}]).

username의 "xxxx" 부분에는 데이터베이스 접근에 사용될 아이디를 적어주시고, password의 "yyyy" 부분에는 해당 사용자의 암호를 적어주시면 된다. database의 "testdb" 부분에는 mysqladmin create <데이터베이스이름> 명령으로 생성한 mysql database 이름을 적어주면 된다.

그런 다음에 erlyweb:compile을 다시 돌려보면 되는데, auto_compile을 true로 해두었다면 사실 안해주어도 무방해야 하지만, 직접 돌려보면 어떤 문제가 있는지를 직접 확인할 수도 있다.

보통 이 단계에서 발생하는 문제는, 다음과 같은 증상으로 나타난다.

** exited: {no_such_table,{{module,test},{table,test}}} **

즉, 모델에 대한 데이터베이스 테이블이 아직 만들어지지 않았다는 것. 이 문제는 mysql 상에서 테이블을 적절히 생성해서 해결해주면 된다.





 

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Erlang2008.06.14 17:03
Erlang 웹 개발을 위해 yaws 서버 소스 파일을 다운로드 받아 설치할때는 다음과 같은 절차를 우선적으로 밟게 된다.

./configure ; make

그런데 ./configure를 실행하면 gcc에서 실행파일을 생성할 수 없다고 하는 메시지가 뜨면서 configure가 중단되는 경우가 있다.

그런 경우에는 이 글을 참고하여 gcc 문제를 먼저 해결해야 한다. 그런 다음에 다시 ./configure ; make 한다.

그런데 make를 진행하다 보면 pam_appl.h라는 헤더 파일을 찾지 못한다는 오류 메시지가 출력되면서 컴파일이 진행되지 않는 경우가 있다.

그런 경우에는 sudo apt-get install libpam0g-dev 를 실행하여 관련된 헤더 파일을 설치하여야 한다.

이상의 과정이 정상적으로 끝났다면 이제 ./configure ; make를 하면 yaws 파일이 정상적으로 생성된다.

생성된 실행 파일을 설치하는 것은 그 나중 문제인데, 사용자의 local home directory에 임시로 설치하여 사용하고자 한다면 그 상태에서 make local_install 하면 된다. 그러면 실행 파일이 사용자의 $HOME/bin에 설치되고, configuration 파일은 $HOME/yaws.conf와 같이 만들어지게 된다.

개발 단계에서는 이렇게 설치해놓고 사용하는 것이 보다 간편하다.

상기 문제는 yaws 소스를 컴파일하여 설치하려고 하는 경우에만 발생하며, sudo apt-get install yaws와 같이 해서 설치한 사용자에게는 발생하지 않는 문제이다. 다만 sudo apt-get install yaws로 설치한 사용자는 Yaws 사용자 가이드에 나온 예제들을 실행해 보기 위애서 여러 가지 설정 파일들을 이리 고치고 저리 고치는 수고를 좀 장시간 해 주어야 할 것이다.

참고한 링크들:

  1. http://marc-abramowitz.com/archives/2007/04/18/building-yaws-erlang-web-server-for-ubuntu/
  2. http://www.buggymind.com/137
  3. http://www.ibm.com/developerworks/kr/library/opendw/20080520/





신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Languages/Erlang2008.06.12 19:16

검토자로 참여했던 Programming Erlang의 한국어 번역판, "프로그래밍 얼랭"을 방금 받았습니다. 이 책을 받아들고 보니, 예전에 APNOMS 2007에서 만났던 Romain Lenglet 이라는 친구가 생각나네요. 이 친구는 그당시 Tokyo의 Orange Labs라는 곳에서 일하고 있었는데, French Telecom 직원이었고, 프로그래밍 언어로 Erlang을 쓴다고 했었습니다. 그런데 "얼랭"이라고 하니까 잘 못알아듣더군요. 그친구는 거의 "랭"과 "랑"의 중간쯤 되는 발음을 헀던 것 같습니다. (제가 술에 취해 있어놔서 잘 못들었을수도 있습니다. ㅋㅋ)

제가 그 친구로부터 얼랭에 관한 이야기를 듣고 관심을 가지기 시작한지 일년만에, 이 책의 번역에 조금이나마 기여를 할 수 있게 된걸 보면, 사람 사이의 인연은 그냥 만들어지는 것은 아닌 모양입니다.

사용자 삽입 이미지

지난번에 이집트 출장을 갔다 오는 길에는 비행기 안에서 피지에서 일하는 의사 한분을 만났습니다. 환갑을 지나 70을 향해 달려가고 있는 분이었는데, 짧은 영어로 여러가지 재미있는 이야기를 나누었었죠. 제가 가지고 있는 지병(?)들에 대한 이야기도 했습니다. 그분은 의사니까, 아무래도 제 병에 관심이 있었겠죠. 그것도 그 분이 가지고 있는 직업병이라면 직업병입니다만..

APNOMS에서 만난 그 친구와 Erlang에 대한 이야기를 나누게 된 것도, 아마 프로그래머가 공통적으로 가지고 있는 직업병이라면 직업병 탓이겠죠. 술먹으면서도 고작 프로그래밍 언어에 대해 이야기를 하고 있다니, 대체 우리는 어떻게 된 사람들인거야, 뭐 이런 소리를 하면서 웃었던 기억도 납니다. 어쨌든, 그런 병(?) 덕분에 저는 새로운 언어를 맛이나마 볼수 있었고, 새 책이 태어나는 데 조금이나마 기여를 할 수가 있었습니다.

 
사용자 삽입 이미지
Romain씨와 만났을 당시, 만취해있었던 접니다. -_-;


가끔은 그런 생각이 듭니다. 내게 주어지는 모든 것에는 다 이유가 있다는, 뭐 그런 생각 말이죠. 물론, 그런 생각을 너무 심각하게 하다 보면 인생이 좀 공포스러워지긴 합니다만.... 위의 만취샷이 주는 공포감에는 비할 수가 없을거 같기도 하군요. -_-

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 비밀댓글입니다

    2008.06.13 13:02 [ ADDR : EDIT/ DEL : REPLY ]
    • 무슨 그런 말씀을 -_-; 충분히 잘 하셨다고 생각합니다. :-)
      좋은 책 내느라 애쓰셨습니다. 열심히 보겠습니다~

      2008.06.13 13:36 신고 [ ADDR : EDIT/ DEL ]

Languages/Erlang2008.04.23 14:06

Erlang 웹사이트에 가서 GS 사용자 가이드를 다운받아 GUI 관련 프로그래밍을 Erlang으로 해 보고 있었는데요. 이거 조금 문제가 있군요. ㅋㅋ Linux에서는 잘 되는 예제가 Windows에서는 좀 이상하게 실행됩니다. 이렇게 되면 Windows 환경에서 GS를 사용해서 프로그래밍하는 것이 좀 어려워 질 수도 있겠군요.

GS 사용자 가이드 chapter 1의 예제 15번입니다. 다음과 같은 GUI를 화면에 그리는 것이 목적입니다.

사용자 삽입 이미지


그런데 이 예제를 Windows에서 실행하면 제대로 되질 않습니다. 왜 그런가 봤더니, 텍스트의 Width와 Height가 전부 똑같이 계산되더군요. 다음과 같이 출력됩니다. Rectangle 크기가 전부 동일하게 계산되는 바람에, 글자 주변에 사각형을 제대로 그릴 수가 없네요. Windows 환경에서 실행되는 gs:read(name, {font_wh, {Font, Text}}) 함수에 버그가 있는 것 같습니다.

사용자 삽입 이미지

화면 캡처도 이상하게 되는군요. ㅋㅋ

신고
Posted by 이병준
TAG Erlang, gs, 버그

소중한 의견, 감사합니다. ^^

  1. 오.. 네트워크에 강한 erlang 공부하시는군요. 행복해 보입니다. 자기가 하고 싶은 것을 한다는 것 말이에요.

    2008.04.28 10:23 신고 [ ADDR : EDIT/ DEL : REPLY ]
    • 그렇게 봐 주시니 감사합니다. 그런데 제가 요령이 없어서 그런지 공부하기가 쉽지 않네요. ^^;

      2008.04.28 13:23 신고 [ ADDR : EDIT/ DEL ]

Languages/Erlang2008.04.17 15:48

이 책의 번역본이 곧 출간될 예정입니다. 저도 리뷰어로 참여해서 몇몇 챕터를 읽어봤습니다. 덕분에, 요즘 Erlang 공부를 하고 있습니다. 원서를 구해서, 지금 8장까지 읽었습니다.

제가 주로 개발하는 프로그램은 네트워크 관리 시스템입니다. SNMP 라이브러리 같은 것도 필요하고, 데이터베이스도 필요하고, 이중화도 해야 하고, .... 이런 시스템을 만들려면 할일이 산더미 같이 많습니다. C++로 구현하려다 보면, 그 하나 하나가 전부 짐처럼 느껴지곤 합니다. 하지만 그래도 C++을 써 온 것은, 제가 가장 익숙한 프로그래밍 언어이기 때문이었습니다.

그런데 Erlang을 배워보니, 이 언어야 말로 네트워크 관리 시스템을 위한 Domain-specific Programming Language가 될 수 있겠다는 느낌이 듭니다. SNMP 관련 기능 뿐 아니라, 이중화를 지원하는 데이터베이스 까지 기본으로 갖추어져 배포되는 언어가, 바로 Erlang이거든요. (물론 이것은 그저 일부일 뿐입니다.) 이런 언어를 몇년 전에만 알았다면 네트워크 관리 시스템 프로그래머로서 제 인생은 어떻게 달라졌을까요? 좀 궁금해집니다.


사용자 삽입 이미지


자세한 것은 더 배워보아야 알 수 있겠습니다만, 네트워크에서 뭔가를 하고자 하는 사람은 이 언어를 한 번 배워 보는 것이 좋겠다는 느낌이 많이 드네요. 책을 읽어 가면서 뭔가 깨달음이 올 때 마다, 한 번씩 글을 올려보도록 하겠습니다.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^