기초
Hello World와 모듈
각 Erlang 파일은 -module(name)으로 선언된 모듈입니다. 내보낸 함수는 -export([name/arity])로 나열됩니다. arity(인자 수)는 함수 식별의 일부입니다 — greet/0과 greet/1은 다른 함수입니다. 셸에서 c(Module)로 컴파일합니다.
% hello.erl
-module(hello).
-export([greet/1, greet/0]).
greet() ->
greet("World").
greet(Name) ->
io:format("Hello, ~s!~n", [Name]).
% Compile & run in the shell:
% c(hello).
% hello:greet("Alice"). %=> Hello, Alice!변수와 원자
변수는 대문자(또는 _)로 시작하고 단일 할당입니다 — 한번 바인딩되면 변경할 수 없습니다. 바인딩된 변수에 재매칭은 어설션입니다: X = X+1은 badmatch 오류를 발생시킵니다. 원자는 소문자로 시작하는 명명 상수로, 식별성으로 비교됩니다.
Name = "Alice". % variables start with uppercase
Age = 30. % bound once — single assignment!
% Age = 31. % ** exception error: no match of RHS
% Atoms start with lowercase (or are quoted)
ok. % the ok atom (common return value)
error. % another atom
'This is an atom'. % quoted atom with spaces
true, false, undefined % built-in atoms
% Pattern matching binds and asserts
{ok, Result} = {ok, 42}. % Result = 42
{error, _} = {error, badarg}. % _ matches and discards튜플과 레코드
튜플은 {a,b,c}로 표기되는 고정 크기 컨테이너입니다. 레코드는 -record로 선언된 명명 필드를 가진 튜플입니다. 필드 갱신은 새 레코드를 만듭니다(불변 데이터). 첫 번째 요소는 관행적으로 태그 원자입니다.
% Tuples (fixed-size, positional)
Point = {point, 3, 4}.
{point, X, Y} = Point. % X=3, Y=4
element(2, Point). %=> point
% Records (syntactic sugar over tuples)
-record(person, {name, age, city = "Unknown"}).
P = #person{name = "Alice", age = 30}.
P#person.name. %=> "Alice"
P2 = P#person{age = 31}. % update field
{person, Name, Age, _} = P. % underlying tuple리스트와 리스트 컴프리헨션
리스트는 [Head|Tail]로 구성된 단일 연결 리스트입니다. ++는 연결하고(왼쪽 O(n)), --는 요소를 제거합니다. 컴프리헨션 [Expr || 한정자1, ...]은 제너레이터(X <- List)와 필터(불리언 식)를 지원합니다.
[1,2,3] ++ [4,5]. %=> [1,2,3,4,5] (append)
[1,2,3] -- [2]. %=> [1,3] (subtract)
hd([1,2,3]). %=> 1 (head)
tl([1,2,3]). %=> [2,3] (tail)
% Pattern matching on lists
[Head | Tail] = [1,2,3]. % Head=1, Tail=[2,3]
[A,B|Rest] = [1,2,3,4]. % A=1, B=2, Rest=[3,4]
% List comprehensions
[ X*2 || X <- [1,2,3,4] ]. %=> [2,4,6,8]
[ X || X <- [1..10], X rem 2 =:= 0 ]. % evens: [2,4,6,8,10]
[ {A,B} || A <- [1,2], B <- [a,b] ]. % cartesian product함수와 패턴 매칭
함수는 ;로 구분된 여러 절로 정의됩니다. 절은 순서대로 시도되고 패턴과 가드가 모두 성공하는 첫 번째 것이 사용됩니다. {ok, Val} / {error, Reason} 같은 태그된 튜플이 예외 없이 성공/실패를 나타내는 표준 방식입니다.
-module(math_tools).
-export([factorial/1, fib/1, classify/1]).
% Multiple clauses selected by pattern matching
factorial(0) -> 1;
factorial(N) when N > 0 -> N * factorial(N - 1).
% Fibonacci with guards
fib(0) -> 0;
fib(1) -> 1;
fib(N) when N > 1 -> fib(N-1) + fib(N-2).
% Tagged returns (common Erlang idiom)
classify(X) when X < 0 -> {negative, X};
classify(0) -> zero;
classify(X) when X > 0 -> {positive, X}.맵
맵(R17 이후)은 키-값 사전입니다. =>로 설정/삽입, :=로 갱신(키가 존재해야 함). 패턴 매칭에서는 :=만 허용됩니다. maps:get/2는 키 부재 시 badarg를 발생시킵니다; maps:get/3으로 기본값을 사용하세요.
% Create
M = #{ name => "Alice", age => 30, role => admin }.
% Lookup
maps:get(name, M). %=> "Alice"
maps:get(city, M, "Unknown"). % default if missing
{ok, V} = maps:find(age, M). %=> {ok, 30}
% Update / add (immutable — returns new map)
M2 = M#{ age => 31, city => "Paris" }.
% Pattern match on maps
#{ name := N } = M. % N = "Alice" (:= for matching, => for setting)
#{ age := A, role := R } = M.패턴 매칭과 가드
함수 헤드에서의 패턴 매칭
Erlang은 위에서 아래로 인자를 패턴 매칭하여 함수 절을 선택합니다. 패턴은 새 변수를 바인딩하고, 리터럴과의 동일성을 단언하며, _로 필드를 무시할 수 있습니다. 같은 메커니즘이 receive, case, =에 쓰입니다.
% Select clause by structure of argument
handle({ping, From}) -> From ! pong;
handle({echo, Msg}) -> Msg;
handle({add, A, B}) -> A + B;
handle(stop) -> bye.
% Tuple destructuring
-record(rect, {w, h}).
area(#rect{w=W, h=H}) -> W * H.
% List patterns with literal values
greet_first([H|_]) -> io:format("Hi ~p~n", [H]);
greet_first([]) -> ok.case 식
case는 식을 평가하고 일련의 패턴과 비교합니다. 각 절은 가드를 가질 수 있습니다. 분기가 원래 인자가 아닌 계산된 값에 의존할 때 표준 제어 흐름 도구입니다. _ 전체 캐치 절을 항상 포함하세요.
classify_file(Size) ->
case Size of
0 -> empty;
N when N < 1024 -> small;
N when N < 1048576 -> medium;
_ -> large
end.
% Matching on tagged tuples
Result = case file:read_file("a.txt") of
{ok, Bin} -> {ok, binary_to_list(Bin)};
{error, enoent} -> {error, missing};
{error, Reason} -> {error, Reason}
end.가드
가드는 절이 언제 매칭되는지 제한합니다. 항상 종료되고 부작용이 없도록 알려진 BIF 집합으로 제한됩니다. 쉼표 = AND, 세미콜론 = OR. 가능하면 가드보다 패턴 매칭을 선호하세요.
% Guards go after 'when' and may use built-in guard tests (BIFs)
max(A, B) when A >= B -> A;
max(_, B) -> B.
% Multiple guard tests: ',' = AND, ';' = OR
classify(N) when N > 0, N < 100 -> in_range;
classify(N) when N =< 0; N >= 100 -> out_of_range.
% Allowed guard BIFs include comparison, arithmetic, type tests:
% is_integer/1, is_atom/1, is_list/1, is_map/1,
% length/1, map_size/1, abs/1, element/2, hd/1, tl/1
% (No user functions — guards must be side-effect free and terminate!)비트 구문과 바이너리
비트 구문 << Seg1, Seg2, ... >>는 비트 필드별로 바이너리를 팩/언팩합니다. 각 세그먼트는 Value:Size/TypeSpecifier이며 Type은 integer|binary|float|bits입니다. 이것이 Erlang을 네트워크 프로토콜과 파일 포맷 파싱에 뛰어나게 만듭니다.
% Pack a 16-bit length + 8-bit type + payload
Bin = << Length:16, Type:8, Payload/binary >>.
% Unpack with the same pattern
<< L:16, T:8, Rest/binary >> = Bin.
% Bit fields & endianness
<< N:32/big-unsigned-integer >> = <<0,0,1,1>>. % N = 261
<< N:32/little-signed-integer >> = <<1,0,0,0>>. % N = 1
% IPv4 header parsing example
<< Version:4, IHL:4, _TOS:8, TotalLen:16,
_/binary >> = Packet.동시 프로그래밍
spawn — 프로세스 시작
spawn(M,F,A)는 새 BEAM 프로세스를 만듭니다 — 자체 힙과 메일박스를 가진 경량 그린 스레드. 프로세스는 메모리를 공유하지 않고 메시지 패싱으로만 통신합니다. BEAM은 수천만 프로세스를 실행할 수 있고 각각 약 2-3 KB입니다.
% spawn/3 starts a new lightweight process
Pid = spawn(Module, Function, ArgsList).
Pid = spawn(fun() -> timer:sleep(1000), io:format("done~n") end).
% Every process has a PID; processes share NO memory
% (BEAM processes are NOT OS threads — millions are possible)
% self() returns the current process's PID
io:format("I am ~p~n", [self()]).
% is_process_alive/1 checks liveness
case is_process_alive(Pid) of
true -> ok;
false -> {error, dead}
end.send (!) — 메시지 패싱
Pid ! Msg는 비동기적으로 수신자의 메일박스에 메시지를 보냅니다. 전달이 보장되고 같은 프로세스 쌍 사이에서 순서가 보존됩니다. !는 메시지 자체를 반환하여 브로드캐스트가 가능합니다. 프로세스는 원자 이름으로 등록할 수 있습니다.
% Send a message: Pid ! Message
Pid ! {hello, self()}.
Pid ! ping.
Pid ! {compute, 1, 2, 3}.
% ! always returns the message itself, so you can broadcast:
[ P ! broadcast || P <- AllPids ].
% Send to a registered process (by atom name)
registered_name ! {request, Ref, self()}.
% Register / unregister a name for a PID
register(server, Pid).
unregister(server).
whereis(server). %=> Pid | undefinedreceive — 패턴 매칭 메시지
receive는 메일박스를 순서대로 스 캔하여 첫 번째 매칭 메시지를 찾습니다; 없으면 도착까지 대기합니다. after 절로 타임아웃을 설정합니다(0 = 비블로킹, infinity = 영원히). 일반적인 서버 패턴은 각 메시지 후 자신을 호출하는 꼬리 재귀 loop()입니다.
% receive blocks until a matching message arrives
loop() ->
receive
{ping, From} ->
From ! pong,
loop();
{echo, Msg} ->
io:format("echo: ~p~n", [Msg]),
loop();
stop ->
ok
end.
% receive with a timeout
receive
{data, X} -> process(X)
after 5000 ->
{error, timeout}
end.
% Flush mailbox (drain all messages)
flush() ->
receive _ -> flush() after 0 -> ok end.선택적·비선택적 receive
receive는 선택적입니다: 더 오래된 메시지가 있어도 첫 번째 매칭을 찾습니다. 고유한 Ref로 특정 응답을 매칭하는 데 적합하지만, 매칭되지 않은 메시지가 누적됩니다. gen_server는 이를 피합니다. make_ref()로 고유 참조를 만드세요.
% Selective receive: skips non-matching messages
get_reply(Ref) ->
receive
{Ref, Reply} -> Reply % only matches the reply with our Ref
after 5000 ->
{error, timeout}
end.
% Problem: other messages stay in the mailbox, slowing future receives.
% For high-throughput servers use a selective receive library or
% gen_server (which keeps its mailbox clean).
% Non-selective (drain in order):
drain_all() ->
receive
Msg -> [Msg | drain_all()]
after 0 ->
[]
end.참조로 요청/응답
call 패턴은 각 요청을 고유 참조(make_ref/0)와 페어링합니다. 클라이언트는 {call, Ref, self(), Request}를 보내고 같은 Ref로 태그된 응답을 기다립니다 — 메일박스의 다른 트래픽에서 자신의 응답을 골라냅니다. gen_server:call의 기초입니다.
% Client: send a tagged request, await the matching reply
call(Server, Request) ->
Ref = make_ref(),
Server ! {call, Ref, self(), Request},
receive
{Ref, Reply} -> Reply
after 5000 ->
exit(timeout)
end.
% Server side:
handle({call, Ref, From, Request}, State) ->
Reply = do_work(Request),
From ! {Ref, Reply},
State.꼬리 재귀와 서버 루프
BEAM 프로세스는 꼬리 재귀로 상태를 유지합니다: 루프가 새 상태를 마지막 식으로 자신을 호출하여 스택이 성장하지 않습니다. 각 receive 절은 업데이트된 상태로 loop/1에 대한 꼬리 호출로 끝납니다.
% A counter server — each message mutates state via recursion
start_counter(Init) -> spawn(fun() -> counter_loop(Init) end).
counter_loop(N) ->
receive
inc -> counter_loop(N + 1);
dec -> counter_loop(N - 1);
{get, From} -> From ! {value, N}, counter_loop(N);
reset -> counter_loop(0)
end.
% Caller:
Pid = start_counter(0).
Pid ! inc.
Pid ! {get, self()}.
receive {value, V} -> io:format("count = ~p~n", [V]) end.링크, 모니터와 에러
link / unlink — 양방향 링크
link/1은 양방향 링크를 만듭니다: 어느 한쪽이 종료되면 다른 쪽이 종료 신호를 받습니다. 기본적으로 그 신호는 수신자도 크래시시킵니다. process_flag(trap_exit, true)로 신호를 {'EXIT', From, Reason} 메시지로 변환하면 슈퍼바이저가 자식 장애를 감지합니다. spawn_link/1은 원자적으로 둘 다 수행합니다.
% link/1 links current process to another
Pid = spawn(fun() -> ... end),
link(Pid).
% If either dies, the other gets an exit signal.
% Default behavior: the linked partner also exits.
% spawn_link does both atomically:
Pid = spawn_link(fun() -> ... end).
% Trap exits to convert death signals into messages:
process_flag(trap_exit, true).
receive
{'EXIT', From, Reason} -> io:format("~p died: ~p~n", [From, Reason])
end.
unlink(Pid). % remove the link (best-effort)monitor / demonitor — 단방향 모니터
monitor(process, Pid)는 단방향입니다: 호출자만 'DOWN' 메시지를 받고 감시되는 프로세스는 영향받지 않습니다. 관찰만 원할 때 link보다 좋습니다. 각 모니터는 고유 Ref를 반환합니다.
% monitor/1: one-way — we observe Pid, but it doesn't observe us
Ref = monitor(process, Pid).
% If Pid dies, we receive:
% {'DOWN', Ref, process, Pid, Reason}
receive
{'DOWN', Ref, process, Pid, normal} -> ok;
{'DOWN', Ref, process, Pid, Reason} -> {error, Reason}
end.
% demonitor/1 removes the monitor
demonitor(Ref).
% demonitor/2 with [flush] also drains any pending 'DOWN' message
demonitor(Ref, [flush]).exit 신호와 이유
exit(Reason)은 현재 프로세스를 종료하고 exit(Pid, Reason)은 Pid에 신호를 보냅니다. 이유가 중요합니다: normal과 shutdown은 전파되지 않고(링크 파트너 생존), 다른 이 유는 전파되어 링크된 파트너를 죽입니다(trap하지 않는 한). kill 이유는 trap 불가 — 최후의 수단입니다.
% Send an exit signal explicitly:
exit(Pid, Reason). % tell Pid to exit with Reason
% Terminate the current process:
exit(Reason). % Reason != normal kills linked partners
% exit/2 with the atom 'kill' is untrappable:
exit(Pid, kill). % Pid dies even if it traps exits.
% (Only the kernel-supervised 'kill' reason cannot be trapped.)
% Reasons:
% normal -> linked partners do NOT die
% shutdown -> linked partners do NOT die (used in graceful stop)
% {shutdown, Term} -> graceful with reason, also non-propagating
% anything else -> linked partners also die (unless trapping)try / catch와 예외
try ... catch Class:Reason은 세 클래스를 처리합니다: throw(명시적 throw/1), error(런타임 에러), exit(exit/1). after 절은 항상 실행되어 정리용입니다. erlang:get_stacktrace()로 스택 트레이스를 캡처합니다.
try
{ok, Bin} = file:read_file(Path),
binary_to_list(Bin)
catch
throw:Term -> {thrown, Term};
error:Reason -> {error, Reason, erlang:get_stacktrace()};
exit:Reason -> {exit, Reason}
after
% always runs (cleanup), regardless of success/failure
file:close(Fd)
end.
% Raise exceptions:
throw({bad, Value}). % catchable as throw
erlang:error(badarg). % catchable as error
exit(timeout). % catchable as exit슈퍼바이저 패턴(수동)
슈퍼바이저는 exit을 트랩하고 spawn_link로 자식을 시작하고 {'EXIT', Pid, _}를 받으면 자식을 재시작합니다. «let it crash» 철학 — 슈퍼바이저를 단순하게, 워커를 빨리 실패시키는 것 — 이 Erlang의 내결함성 핵심입니다.
% A minimal supervisor: start a child, restart on death
start_sup(Mod) ->
process_flag(trap_exit, true),
Pid = spawn_link(fun() -> Mod:run() end),
sup_loop(Mod, Pid).
sup_loop(Mod, Pid) ->
receive
{'EXIT', Pid, Reason} ->
io:format("child died ~p, restarting~n", [Reason]),
NewPid = spawn_link(fun() -> Mod:run() end),
sup_loop(Mod, NewPid);
{stop, From} ->
exit(Pid, shutdown),
From ! ok
end.OTP gen_server(개요)
gen_server 비헤이비어 골격
-behaviour(gen_server) 구현은 컴파일러가 필수 콜백 함수를 검사하게 합니다. 서버 상태는 init/1 -> handle_call/3(동기)/ handle_cast/2(비동기)-> terminate/2를 흐릅니다. {local, Name} 등록으로 클라이언트가 원자로 호출합니다.
-module(counter).
-behaviour(gen_server).
-export([start_link/0, inc/0, get/0]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
inc() -> gen_server:cast(?MODULE, inc).
get() -> gen_server:call(?MODULE, get).
init(N) -> {ok, N}.
handle_call(get, _F, N) -> {reply, N, N}.
handle_cast(inc, N) -> {noreply, N + 1}.
handle_info(_, N) -> {noreply, N}.
terminate(_, _) -> ok.
code_change(_, N, _) -> {ok, N}.call vs cast
call은 동기적이고 서버가 응답할 때까지 호출자를 블록합니다(또는 5s 타임아웃 후 종료). cast는 비동기이고 항상 즉시 ok를 반환합니다. call은 참조 기반 요청/응답 패턴에 기반합니다.
% Synchronous call — waits for reply, crashes on server death
{ok, Value} = gen_server:call(Server, {get, Key}).
% Asynchronous cast — fire and forget
ok = gen_server:cast(Server, {put, Key, Value}).
% Call supports timeout and monitor under the hood:
gen_server:call(Server, Req, 5000).
% Server replies in handle_call:
handle_call({get, Key}, From, State) ->
{reply, maps:get(Key, State), State}.
% handle_cast never replies:
handle_cast({put, K, V}, State) ->
{noreply, maps:put(K, V, State)}.handle_info와 기타 콜백
handle_info/2는 비 call/cast 메시지를 모두 캡처합니다 — Pid ! Msg, 'DOWN', 타임아웃, 소켓 데이터 등. terminate/2는 리소스를 해제하지만 강제 kill에서는 보장되지 않습니다. code_change/3이 핫 코드 업그레이드를 지원합니다.
% handle_info/2 handles messages NOT from call/cast:
% - plain messages from other processes (Pid ! Msg)
% - 'DOWN' messages from monitors
% - timeouts sent by gen_server:start(..., Timeout)
handle_info({'DOWN', Ref, process, Pid, Reason}, State) ->
io:format("monitored ~p died: ~p~n", [Pid, Reason]),
{noreply, State};
handle_info({tcp, Socket, Data}, State) ->
%% handle incoming socket data
{noreply, State};
handle_info(Info, State) ->
{noreply, State}.
% terminate/2 runs on shutdown — close fds, flush, etc.
terminate(Reason, State) ->
io:format("shutting down: ~p~n", [Reason]),
ok.
% code_change/3 enables hot code upgrade.
code_change(_OldVsn, State, _Extra) -> {ok, State}.애플리케이션 구조(OTP)
OTP 애플리케이션은 모듈, 의존성, 진입 모듈(mod)을 기술하는 .app 파일을 가진 자체 완비 컴포넌트입니다. start/2가 최상위 슈퍼바이저 PID를 반환하고 그것이 워커를 시작합니다. 릴리스가 애플리케이션을 실행 가능한 시스템으로 묶습니다.
% myapp.app — application resource file
{application, myapp,
[{description, "Demo app"},
{vsn, "1.0.0"},
{modules, [myapp_app, myapp_sup, counter]},
{registered, [myapp_sup]},
{applications, [kernel, stdlib]},
{mod, {myapp_app, []}},
{env, [{port, 4000}]}
]}.
% myapp_app.erl — application behaviour
-module(myapp_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_StartType, _StartArgs) -> myapp_sup:start_link().
stop(_State) -> ok.
% Start everything:
% application:start(myapp).
% Or with release: erl -eval 'application:ensure_all_started(myapp)'관련 Erlang 스니펫
Copy-paste ready code for common tasks.
Ping-Pong 프로세스
두 프로세스가 spawn과 receive로 메시지를 주고받는다.
gen_server 카운터
gen_server behaviour로 상태 저장 서버를 구축한다.
수퍼바이저 트리
워커를 시작하고 충돌 시 재시작하는 수퍼바이저를 정의한다.
참조를 이용한 선택적 수신
고유 참조로 여러 메시지 중 특정 응답을 골라낸다.
링크, exit 트래핑과 'let it crash'
link와 trap_exit로 프로세스 죽음을 감지하고 복구한다.
꼬리 재귀로 상태 저장 서버
재귀 호출로 상태를 전달해 프로세스 내에 가변 상태를 유지한다.
rpc:pmap으로 병렬 맵
프로세스 간에 리스트 각 요소에 병렬로 함수를 적용한다.
핫 코드 업그레이드
실행 중인 시스템을 중지하지 않고 모듈 코드를 다시 로드한다.
Was this helpful?