표현식

표현식 (Expressions)

이번 글에서는 에를랭에서 유효한 모든 표현식(expression)을 다룹니다. 에를랭 프로그램을 작성할 때는 매크로와 레코드 표현식도 사용할 수 있어요. 다만 이 표현식들은 컴파일 중에 확장되므로, 그런 의미에서 진짜 에를랭 표현식은 아닙니다. 매크로와 레코드 표현식은 별도 섹션에서 다룹니다:

출처: Erlang 공식 문서 - Expressions

표현식 평가 (Expression Evaluation)

명시적으로 달리 말하지 않는 한, 한 표현식이 평가되기 전에 모든 하위 표현식이 평가됩니다. 예를 들어 다음 표현식을 생각해 볼게요:

Expr1 + Expr2

표현식이기도 한 Expr1Expr2는 덧셈이 수행되기 전에 — 임의의 순서로 — 먼저 평가됩니다.

많은 연산자는 특정 타입의 인자에만 적용될 수 있어요. 예를 들어 산술 연산자는 숫자에만 적용할 수 있습니다. 잘못된 타입의 인자는 badarg 런타임 오류를 일으킵니다.

용어 (Terms)

가장 단순한 형태의 표현식은 용어(term)입니다. 즉 t:integer/0, t:float/0, t:atom/0, t:string/0, t:list/0, t:map/0, t:tuple/0 중 하나예요. 반환 값은 용어 그 자체입니다.

변수 (Variables)

변수는 하나의 표현식입니다. 변수가 값에 바인딩되어 있으면 반환 값은 그 값이에요. 바인딩되지 않은 변수는 패턴에서만 허용됩니다.

변수는 대문자 또는 밑줄(_)로 시작합니다. 변수는 영숫자 문자, 밑줄, @를 포함할 수 있어요.

예시:

X
Name1
PhoneNumber
Phone_number
_
_Height
name@node

변수는 패턴 매칭으로 값에 바인딩됩니다. 에를랭은 _단일 할당(single assignment)_을 사용하는데, 즉 변수는 한 번만 바인딩될 수 있어요.

_익명 변수(anonymous variable)_는 밑줄(_)로 표기하며, 변수가 필요하지만 그 값을 무시해도 될 때 사용할 수 있습니다.

예시:

[H|_] = [1,2,3]

밑줄(_)로 시작하는 변수, 예를 들어 _Height는 일반 변수이지 익명이 아니에요. 다만 컴파일러가 경고를 생성하지 않는다는 의미에서 무시됩니다.

예시:

다음 코드:

member(_, []) ->
    [].

는 더 읽기 쉽게 다시 쓸 수 있어요:

member(Elem, []) ->
    [].

이렇게 하면 미사용 변수 Elem에 대한 경고가 발생합니다. 경고를 피하려면 코드를 다음과 같이 다시 쓰면 됩니다:

member(_Elem, []) ->
    [].

밑줄로 시작하는 변수는 익명이 아니므로 다음 예시는 매칭된다는 점에 주의하세요:

{_,_} = {1,2}

하지만 이 예시는 실패합니다:

{_N,_N} = {1,2}

변수의 스코프는 그 함수 절(clause)입니다. if, case, receive 표현식의 한 분기에서 바인딩된 변수는, 표현식 밖에서 값을 가지려면 모든 분기에서 바인딩되어야 해요. 그렇지 않으면 표현식 밖에서는 unsafe한 것으로 간주됩니다.

try 표현식의 경우 변수 스코프가 제한되어, 표현식 안에서 바인딩된 변수는 표현식 밖에서 항상 unsafe합니다.

패턴 (Patterns)

패턴은 용어와 같은 구조를 가지지만 바인딩되지 않은 변수를 포함할 수 있어요.

예시:

Name1
[H|T]
{error,Reason}

패턴은 절 헤드, case 표현식, receive 표현식, 매칭 표현식에서 허용됩니다.

복합 패턴 연산자 (The Compound Pattern Operator)

Pattern1Pattern2가 유효한 패턴이면 다음도 유효한 패턴입니다:

Pattern1 = Pattern2

용어에 대해 매칭될 때 Pattern1Pattern2 둘 다 그 용어에 매칭됩니다. 이 기능의 아이디어는 용어의 재구성을 피하는 것입니다.

예시:

f({connect,From,To,Number,Options}, To) ->
    Signal = {connect,From,To,Number,Options},
    ...;
f(Signal, To) ->
    ignore.

대신 다음과 같이 쓸 수 있어요:

f({connect,_,To,_,_} = Signal, To) ->
    ...;
f(Signal, To) ->
    ignore.

복합 패턴 연산자는 피연산자가 특정 순서로 매칭된다는 뜻이 아니에요. 즉 Pattern1에서 변수를 바인딩하고 그것을 Pattern2에서 사용하거나, 그 반대는 허용되지 않습니다.

패턴 속의 문자열 접두어 (String Prefix in Patterns)

문자열을 매칭할 때 다음은 유효한 패턴입니다:

f("prefix" ++ Str) -> ...

이것은 동등하지만 읽기 어려운 다음의 문법적 설탕(syntactic sugar)이에요:

f([$p,$r,$e,$f,$i,$x | Str]) -> ...

패턴 속의 표현식 (Expressions in Patterns)

산술 표현식은 다음 두 조건을 모두 충족하면 패턴 안에서 사용할 수 있습니다:

  • 숫자 또는 비트 연산자만 사용한다.
  • 컴파일될 때 그 값을 상수로 평가할 수 있다.

예시:

case {Value, Result} of
    {?THRESHOLD+1, ok} -> ...

매칭 연산자 (The Match Operator)

다음은 PatternExpr에 매칭합니다:

Pattern = Expr

매칭이 성공하면 패턴 안의 바인딩되지 않은 변수가 바인딩되고 Expr의 값이 반환됩니다.

여러 매칭 연산자가 연속으로 적용되면 오른쪽에서 왼쪽으로 평가됩니다.

매칭이 실패하면 badmatch 런타임 오류가 발생합니다.

예시:

1> {A, B} = T = {answer, 42}.
{answer,42}
2> A.
answer
3> B.
42
4> T.
{answer,42}
5> {C, D} = [1, 2].
** exception error: no match of right-hand side value [1,2]

여러 매칭 연산자는 오른쪽에서 왼쪽으로 평가되므로, 다음:

Pattern1 = Pattern2 = . . . = PatternN = Expression

는 이것과 동등합니다:

Temporary = Expression,
PatternN = Temporary,
   .
   .
   .,
Pattern2 = Temporary,
Pattern1 = Temporary

매칭 연산자와 복합 패턴 연산자 (The Match Operator and the Compound Pattern Operator)

Note {: .info }

이것은 아직 소개되지 않은 주제를 언급하는 고급 섹션이에요. 첫 읽기에서는 건너뛰어도 안전합니다.

= 문자는 비슷하지만 구별되는 두 연산자, 즉 매칭 연산자와 복합 패턴 연산자를 나타내는 데 사용됩니다. 어느 쪽인지는 문맥으로 결정됩니다.

_복합 패턴 연산자(compound pattern operator)_는 두 패턴으로 복합 패턴(constituent pattern)을 구성하는 데 쓰입니다. 복합 패턴은 패턴이 허용되는 모든 곳에서 받아들여집니다. 복합 패턴은 그것을 구성하는 모든 패턴이 매칭되면 매칭됩니다. 복합 패턴의 일부인 한 패턴이 (맵 패턴의 키나 바이너리 패턴의 크기처럼) 같은 복합 패턴의 다른 하위 패턴에서 바인딩된 변수를 사용하는 것은 허용되지 않아요.

예시:

1> fun(#{Key := Value} = #{key := Key}) -> Value end.
* 1:7: variable 'Key' is unbound
2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}).
{{1,2},3}
3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>).
{42,43,10795}

_매칭 연산자(match operator)_는 표현식이 허용되는 모든 곳에서 허용됩니다. 표현식의 값을 패턴에 매칭하는 데 쓰여요. 여러 매칭 연산자가 연속으로 적용되면 오른쪽에서 왼쪽으로 평가됩니다.

예시:

1> M = #{key => key2, key2 => value}.
#{key => key2,key2 => value}
2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
value
3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
value
4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
* 1:12: variable 'Key' is unbound
5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
42

프롬프트 2>의 표현식은 먼저 변수 M의 값을 패턴 #{key := Key}에 매칭해 변수 Key를 바인딩합니다. 그런 다음 M의 값을 패턴 #{Key := Value}에 매칭하는데, 여기서 Key를 키로 사용해 변수 Value를 바인딩해요.

프롬프트 3>의 표현식은 표현식 (#{key := Key} = M)을 패턴 #{Key := Value}에 매칭합니다. 괄호 안의 표현식이 먼저 평가돼요. 즉 M#{key := Key}에 매칭된 다음, M의 값이 패턴 #{Key := Value}에 매칭됩니다. 이것은 _2_와 같은 평가 순서이므로, 괄호는 중복됩니다.

프롬프트 4>의 표현식에서 표현식 M은 괄호 안의 패턴에 매칭됩니다. 괄호 안의 구성은 패턴이므로, 두 패턴을 나누는 =는 매칭 연산자가 아니라 복합 패턴 연산자입니다. 두 하위 패턴이 동시에 매칭되므로 매칭은 실패합니다. 따라서 패턴 #{Key := Value}에 매칭할 때 변수 Key는 바인딩되어 있지 않아요.

프롬프트 5>의 표현식에서 블록 표현식 안의 표현식들이 먼저 평가되어 변수 Y를 바인딩하고 바이너리를 만듭니다. 그런 다음 그 바이너리가 Y의 값을 세그먼트 크기로 사용해 패턴 <<X:Y>>에 매칭됩니다.

함수 호출 (Function Calls)

ExprM:ExprF(Expr1,...,ExprN)
ExprF(Expr1,...,ExprN)

함수 호출의 첫 번째 형태 ExprM:ExprF(Expr1,...,ExprN)에서 ExprMExprF는 각각 원자이거나 원자로 평가되는 표현식이어야 합니다. 이 함수는 _완전히 자격을 갖춘 함수 이름(fully qualified function name)_으로 호출된다고 합니다. 이것을 흔히 원격(remote) 또는 _외부(external) 함수 호출_이라고 해요.

예시:

lists:keyfind(Name, 1, List)

함수 호출의 두 번째 형태 ExprF(Expr1,...,ExprN)에서 ExprF는 원자이거나 펀으로 평가되어야 합니다.

ExprF가 원자이면 함수는 _암묵적으로 자격을 갖춘 함수 이름(implicitly qualified function name)_으로 호출된다고 해요. 함수 ExprF가 로컬로 정의되어 있으면 그 함수가 호출됩니다. 또는 ExprFM 모듈에서 명시적으로 import되었다면 M:ExprF(Expr1,...,ExprN)이 호출됩니다. ExprF가 로컬로 선언되지도, 명시적으로 import되지도 않았다면 ExprF는 자동으로 import된 BIF의 이름이어야 합니다.

예시:

handle(Msg, State)
spawn(m, init, [])

ExprF가 펀인 예시:

1> Fun1 = fun(X) -> X+1 end,
Fun1(3).
4
2> fun lists:append/2([1,2], [3,4]).
[1,2,3,4]
3>

로컬 함수를 호출할 때는 암묵적 또는 완전히 자격을 갖춘 함수 이름을 쓰는 것 사이에 차이가 있다는 점을 주의하세요. 후자는 항상 모듈의 최신 버전을 가리킵니다. Compilation and Code LoadingFunction Evaluation을 참고하세요.

로컬 함수 이름이 자동 import된 BIF와 충돌할 때 (Local Function Names Clashing With Auto-Imported BIFs)

로컬 함수가 자동 import된 BIF와 같은 이름을 가지면, 암묵적으로 자격을 갖춘 함수 호출은 BIF가 아니라 로컬에 정의된 함수로 향한다는 의미예요. 혼동을 피하기 위해 -compile({no_auto_import,[F/A]})이라는 컴파일러 지시어가 있는데, 이 지시어는 BIF가 자동 import되는 것을 막습니다. 어떤 상황에서는 이런 컴파일 지시어가 필수적이에요.

Change {: .info }

Erlang/OTP R14A(ERTS 버전 5.8) 이전에는, 자동 import된 BIF와 같은 이름을 가진 함수에 대한 암묵적으로 자격을 갖춘 함수 호출이 항상 BIF를 호출했어요. 새 버전의 컴파일러에서는 로컬 함수가 대신 호출됩니다. 이것은 미래에 자동 import BIF 목록에 추가되는 것이 조용히 옛 코드의 동작을 바꾸는 것을 피하기 위함입니다.

다만 Erlang/OTP 버전 R14A 이상으로 컴파일할 때 옛(pre R14) 코드가 동작을 바꾸지 않도록 다음 제한이 적용됩니다: R14A 이전(ERTS 버전 5.8) OTP 버전에서 자동 import됐던 BIF의 이름을 재정의하고, 그 함수에 대한 암묵적으로 자격을 갖춘 호출을 코드에 가지고 있다면, 컴파일러 지시어로 자동 import를 명시적으로 제거하거나 그 호출을 완전히 자격을 갖춘 함수 호출로 바꿔야 해요. 그렇지 않으면 컴파일 오류가 납니다. 다음 예시를 보세요:

-export([length/1,f/1]).

-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported

length([]) ->
    0;
length([H|T]) ->
    1 + length(T). %% Calls the local function length/1

f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
                                  %% which is allowed in guards
    long.

같은 논리가 다른 모듈에서 명시적으로 import된 함수에도 로컬 정의 함수와 마찬가지로 적용됩니다. 다른 모듈에서 함수를 import하면서 동시에 그 함수를 모듈 안에서 선언하는 것은 허용되지 않아요:

-export([f/1]).

-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported

-import(mod,[length/1]).

f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
                                   %% which is allowed in guards

    erlang:length(X);              %% Explicit call to erlang:length in body

f(X) ->
    length(X).                     %% mod:length/1 is called

Erlang/OTP R14A 이후에 추가된 자동 import BIF의 경우, 그 이름을 로컬 함수나 명시적 import로 재정의하는 것은 항상 허용됩니다. 다만 -compile({no_auto_import,[F/A]}) 지시어를 쓰지 않으면, 컴파일러는 모듈 안에서 그 함수가 암묵적으로 자격을 갖춘 함수 이름으로 호출될 때마다 경고를 내보냅니다.

If

if
    GuardSeq1 ->
        Body1;
    ...;
    GuardSeqN ->
        BodyN
end

if 표현식의 분기는, true로 평가되는 가드 시퀀스 GuardSeq를 찾을 때까지 순서대로 훑습니다. 그러면 그에 대응하는 Body(,로 구분된 표현식 시퀀스)가 평가됩니다.

Body의 반환 값이 if 표현식의 반환 값이에요.

true로 평가되는 가드 시퀀스가 없으면 if_clause 런타임 오류가 발생합니다. 필요하다면 마지막 분기에서 가드 표현식 true를 사용할 수 있는데, 그 가드 시퀀스는 항상 true이기 때문입니다.

예시:

is_greater_than(X, Y) ->
    if
        X > Y ->
            true;
        true -> % works as an 'else' branch
            false
    end

Case

case Expr of
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
end

표현식 Expr이 평가되고 패턴 Pattern이 그 결과에 순서대로 매칭됩니다. 매칭이 성공하고 선택적 가드 시퀀스 GuardSeq가 true이면 그에 대응하는 Body가 평가됩니다.

Body의 반환 값이 case 표현식의 반환 값이에요.

true인 가드 시퀀스를 가진 매칭 패턴이 없으면 case_clause 런타임 오류가 발생합니다.

예시:

is_valid_signal(Signal) ->
    case Signal of
        {signal, _What, _From, _To} ->
            true;
        {signal, _What, _To} ->
            true;
        _Else ->
            false
    end.

Maybe

Change {: .info }

maybe feature는 Erlang/OTP 25에서 도입됐어요. Erlang/OTP 27부터 기본적으로 활성화되어 있습니다.

maybe
    Expr1,
    ...,
    ExprN
end

maybe 블록 안의 표현식들은 순차적으로 평가됩니다. 모든 표현식이 성공적으로 평가되면 maybe 블록의 반환 값은 ExprN이에요. 다만 조건부 매칭 표현식으로 실행을 단락(short-circuit)시킬 수 있습니다:

Expr1 ?= Expr2

?=를 조건부 매칭 연산자(conditional match operator)라고 해요. maybe 블록의 최상위에서만 사용이 허용됩니다. 패턴 Expr1Expr2에 매칭합니다. 매칭이 성공하면 패턴 안의 바인딩되지 않은 변수가 바인딩됩니다. 만약 이 표현식이 maybe 블록의 마지막 표현식이면 Expr2의 값도 반환해요. 매칭이 실패하면 maybe 블록의 나머지 표현식들을 건너뛰고 maybe 블록의 반환 값은 Expr2입니다.

maybe 블록 안에서 바인딩된 변수는 블록 뒤에 오는 코드에서 사용해서는 안 됩니다.

다음은 예시입니다:

maybe
    {ok, A} ?= a(),
    true = A >= 0,
    {ok, B} ?= b(),
    A + B
end

먼저 a(){ok,42}를, b(){ok,58}을 반환한다고 가정해 볼게요. 그 반환 값들로 모든 매칭 연산자가 성공하고, maybe 블록의 반환 값은 A + B, 즉 42 + 58 = 100입니다.

이제 a()error를 반환한다고 가정해 봐요. {ok, A} ?= a()의 조건부 매칭 연산자는 매칭에 실패하고, maybe 블록의 반환 값은 매칭에 실패한 표현식의 값, 즉 error입니다. 마찬가지로 b()wrong을 반환하면 maybe 블록의 반환 값은 wrong입니다.

마지막으로 a(){ok,-1}을 반환한다고 가정해 볼게요. true = A >= 0은 매칭 연산자 =를 사용하므로, 그 표현식이 패턴에 매칭에 실패하면 {badmatch,false} 런타임 오류가 발생합니다.

이 예시는 중첩된 case 표현식을 사용해 덜 간결하게도 쓸 수 있어요:

case a() of
    {ok, A} ->
        true = A >= 0,
        case b() of
            {ok, B} ->
                A + B;
            Other1 ->
                Other1
        end;
    Other2 ->
        Other2
end

maybe 블록은 else 절로 확장할 수 있습니다:

maybe
    Expr1,
    ...,
    ExprN
else
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
end

조건부 매칭 연산자가 실패하면, 실패한 표현식이 elseend 키워드 사이의 모든 절에 있는 패턴에 매칭됩니다. 매칭이 성공하고 선택적 가드 시퀀스 GuardSeq가 true이면 그에 대응하는 Body가 평가됩니다. 본문에서 반환된 값이 maybe 블록의 반환 값이에요.

true인 가드 시퀀스를 가진 매칭 패턴이 없으면 else_clause 런타임 오류가 발생합니다.

maybe 블록에서 바인딩된 변수는 else 절에서 사용해서는 안 됩니다. else 절에서 바인딩된 변수는 maybe 블록 뒤에 오는 코드에서 사용해서는 안 됩니다.

다음은 else 절로 확장된 이전 예시입니다:

maybe
    {ok, A} ?= a(),
    true = A >= 0,
    {ok, B} ?= b(),
    A + B
else
    error -> error;
    wrong -> error
end

else 절은 조건부 매칭 연산자에서 나온 실패 값을 error 값으로 변환합니다. 실패 값이 인식된 값 중 하나가 아니면 else_clause 런타임 오류가 발생해요.

보내기 (Send)

Expr1 ! Expr2

Expr2의 값을 Expr1이 지정하는 프로세스에 메시지로 보냅니다. Expr2의 값이 이 표현식의 반환 값이기도 해요.

Expr1은 pid, 별명(alias, reference), 포트, 등록된 이름(원자), 또는 튜플 {Name,Node}로 평가되어야 합니다. Name은 원자이고 Node는 노드 이름으로 역시 원자입니다.

  • Expr1이 이름으로 평가되는데 그 이름이 등록되어 있지 않으면 badarg 런타임 오류가 발생합니다.
  • reference에 메시지를 보내는 것은, 그 reference가 더 이상(또는 애초에) 별명이 아니어도 절대 실패하지 않습니다.
  • pid에 메시지를 보내는 것은, 그 pid가 존재하지 않는 프로세스를 식별하더라도 절대 실패하지 않아요.
  • 분산 메시지 전송, 즉 Expr1이 튜플 {Name,Node}(또는 다른 노드에 위치한 pid)로 평가되면 역시 절대 실패하지 않습니다.

Receive

receive
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
end

receive 표현식은 메시지 큐에서 receive 표현식의 절에 있는 패턴 중 하나와 매칭되는 메시지를 찾습니다. 절의 패턴들은 위에서 아래로 메시지에 매칭됩니다. 메시지 큐의 시작부터 첫 번째로 매칭되는 메시지가 선택됩니다. 메시지는 보통 받은 순서대로 메시지 큐에 들어갑니다. 다만 받는 프로세스가 우선 메시지 수신을 활성화했다면 항상 그런 것은 아니에요. 매칭이 성공하고 선택적 가드 시퀀스 GuardSeq가 true이면, 매칭된 메시지가 메시지 큐에서 꺼내지고 그에 대응하는 Body가 평가됩니다. 메시지 큐의 다른 모든 메시지는 변하지 않습니다.

Body의 반환 값이 receive 표현식의 반환 값입니다.

receive는 절대 실패하지 않습니다. 패턴 중 하나와 매칭되고 true인 가드 시퀀스를 가진 메시지가 도착할 때까지, 실행은 아마도 무기한 중단됩니다.

Warning {: .warning }

receive 표현식의 시간 복잡도는 O(N)인데, 여기서 N은 메시지 큐에서 매칭되는 메시지보다 앞에 있는 메시지 수에 해당합니다. 즉 receive 표현식의 패턴 조합이 특정 메시지만 매칭하고 메시지 큐가 거대하면, 그런 receive 표현식을 실행하는 것이 매우 비싸질 수 있어요.

다만 특정 패턴만 매칭하는 receive 표현식의 한 종류는 컴파일러와 런타임 시스템이 최적화할 수 있는데, 그것은 reference를 만들고 그 reference를 만든 곳과 가까운 receive 표현식의 모든 절에서 그것에 매칭할 때입니다. 이 경우 reference를 만든 뒤에 받은 메시지만 조사하면 됩니다. 자세한 내용은 Efficiency GuideFetching Received Messages 섹션을 참고하세요.

예시:

wait_for_onhook() ->
    receive
        onhook ->
            disconnect(),
            idle();
        {connect, B} ->
            B ! {busy, self()},
            wait_for_onhook()
    end.

receive 표현식은 타임아웃으로 확장할 수 있습니다:

receive
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
after
    ExprT ->
        BodyT
end

receive...afterreceive와 똑같이 동작하지만, ExprT 밀리초 안에 매칭되는 메시지가 도착하지 않으면 BodyT가 대신 평가된다는 점이 달라요. 그러면 BodyT의 반환 값이 receive...after 표현식의 반환 값이 됩니다. ExprT는 정수 또는 원자 infinity로 평가되어야 해요. 허용되는 정수 범위는 0부터 4294967295까지, 즉 가장 긴 타임아웃은 거의 50일입니다. 값이 0이면 메시지 큐에 매칭되는 메시지가 없을 때 타임아웃이 즉시 발생합니다.

Warning {: .warning }

after 0 절(또는 다른 짧은 타임아웃)이 있는 receive 표현식은 타임아웃이 짧으므로 저렴해 보일 수 있어요. 하지만 반드시 그런 것은 아닙니다. receive 표현식의 절에 있는 패턴이 특정 메시지만 매칭하고 그런 메시지가 메시지 큐에 없으면, 타임아웃이 발생하기 전에 전체 메시지 큐를 조사해야 합니다. 즉 위의 경고와 같은 주의사항이 적용됩니다.

원자 infinity는 프로세스가 매칭되는 메시지를 무기한 기다리게 합니다. 타임아웃을 쓰지 않는 것과 같아요. 런타임에 계산되는 타임아웃 값에 유용할 수 있습니다.

예시:

wait_for_onhook() ->
    receive
        onhook ->
            disconnect(),
            idle();
        {connect, B} ->
            B ! {busy, self()},
            wait_for_onhook()
    after
        60000 ->
            disconnect(),
            error()
    end.

분기가 없는 receive...after 표현식을 사용하는 것도 법적으로 허용됩니다:

receive
after
    ExprT ->
        BodyT
end

이 구성은 어떤 메시지도 소비하지 않고, ExprT 밀리초 동안 프로세스의 실행을 중단할 뿐입니다. 간단한 타이머를 구현하는 데 쓸 수 있어요.

예시:

timer() ->
    spawn(m, timer, [self()]).

timer(Pid) ->
    receive
    after
        5000 ->
            Pid ! timeout
    end.

일반적으로 에를랭의 타이머에 대한 더 많은 정보는 ERTS User's Guide의 Time and Time Correction in Erlang에 있는 Timers 섹션을 참고하세요.

용어 비교 (Term Comparisons)

Expr1 op Expr2
op Description
== Equal to
/= Not equal to
=< Less than or equal to
< Less than
>= Greater than or equal to
> Greater than
=:= Term equivalence
=/= Term non-equivalence

Table: Term Comparison Operators.

인자는 서로 다른 데이터 타입일 수 있습니다. 다음 순서가 정의됩니다:

number < atom < reference < fun < port < pid < tuple < map < nil < list < bit string

앞의 표현식에서 nil은 빈 리스트([])를 나타내며, t:list/0과는 별개의 타입으로 간주됩니다. 그래서 nil < list인 거예요.

리스트는 요소별로 비교됩니다. 튜플은 크기로 정렬되고, 크기가 같은 두 튜플은 요소별로 비교됩니다.

비트 문자열은 비트별로 비교됩니다. 한 비트 문자열이 다른 것의 접두어이면 더 짧은 비트 문자열이 더 작은 것으로 간주됩니다.

맵은 크기로 정렬되고, 크기가 같은 두 맵은 오름차순 용어 순서로 키로, 그다음 키 순서로 값으로 비교됩니다. 맵 키 순서에서 정수 타입은 float 타입보다 작은 것으로 간주됩니다.

원자는 그 문자열 값으로 코드포인트별로 비교됩니다.

정수를 float와 비교할 때 정밀도가 낮은 용어가 다른 용어의 타입으로 변환됩니다(연산자가 =:==/=인 경우는 제외). float는 모든 유효 숫자가 소수점 왼쪽에 올 때까지 정수보다 정밀합니다. 이것은 float가 +/-9007199254740992.0보다 크거나 작을 때 일어나요. 크고 작은 float와 정수의 비교가 추이성(transitivity)을 잃지 않도록 변환 전략은 float의 크기에 따라 바뀝니다.

용어 동치 연산자 =:==/=는 두 용어가 서로 구별할 수 없는지 여부를 반환합니다. 다른 연산자들이 타입이 달라도 같은 _숫자_를 같다고 간주하는 반면(1 == 1.0은 true), 용어 동치 연산자는 인자를 구별할 방법이 존재하는지 여부를 반환해요.

예를 들어 용어 00.0은 같은 _숫자_를 나타내지만, is_integer/1 함수로 구별할 수 있습니다. 따라서 =:==/=는 그것들을 다르다고 봅니다.

게다가 용어 0.0-0.0도 같은 _숫자_를 나타내지만, float_to_list/1로 문자열로 변환하면 다른 결과를 냅니다. 전자에 대해서는 부호 없는 문자열을, 후자에 대해서는 부호 있는 문자열을 반환하거든요. 따라서 =:==/=는 그것들을 다르다고 봅니다.

용어 동치 연산자는 용어를 불투명한 값으로 추론할 때 유용합니다. 예를 들어 연관 컨테이너나 메모이즈된 함수에서 같음 연산자(==)를 쓰면 서로 다른 타입의 숫자가 뒤섞여 잘못된 결과가 생길 위험이 있는 경우예요.

용어 비교 연산자는 표현식의 불리언 값 true 또는 false를 반환합니다.

예시:

1> 1 == 1.0.
true
2> 1 =:= 1.0.
false
3> 0 =:= 0.0.
false
4> 0.0 =:= -0.0.
false
5> 0.0 =:= +0.0.
true
6> 1 > a.
false
7> #{c => 3} > #{a => 1, b => 2}.
false
8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}.
true
9> <<2:2>> < <<128>>.
true
10> <<3:2>> < <<128>>.
false

Note {: .info }

OTP 27 이전에는 용어 동치 연산자가 0.0-0.0을 같은 용어로 간주했어요.

이것은 OTP 27에서 바뀌었지만 레거시 코드는 그것들이 같다고 기대했을 수 있어요. 업그레이드에서 생길 수 있는 오류를 잡도록 돕기 위해, 컴파일러는 0.0이 패턴 매칭되거나 용어 동치 테스트에 사용될 때 경고를 발생시킵니다.

0.0을 구체적으로 매칭해야 한다면 +0.0을 쓰면 경고가 조용해져요. +0.0은 같은 용어를 만들지만 컴파일러가 그 매칭이 의도적으로 이루어진 것으로 해석하게 합니다.

산술 표현식 (Arithmetic Expressions)

op Expr
Expr1 op Expr2
Operator Description Argument Type
+ Unary + Number
- Negation (unary -) Number
+ Addition Number
- Subtraction Number
* Multiplication Number
/ Floating-point division Number
bnot Unary bitwise NOT Integer
div Integer division Integer
rem Integer remainder of X/Y Integer
band Bitwise AND Integer
bor Bitwise OR Integer
bxor Bitwise XOR Integer
bsl Bitshift left Integer
bsr Arithmetic bitshift right Integer

Table: Arithmetic Operators.

예시:

1> +1.
1
2> -1.
-1
3> 1+1.
2
4> 4/2.
2.0
5> 5 div 2.
2
6> 5 rem 2.
1
7> 2#10 band 2#01.
0
8> 2#10 bor 2#01.
3
9> a + 10.
** exception error: an error occurred when evaluating an arithmetic expression
     in operator  +/2
        called as a + 10
10> 1 bsl (1 bsl 64).
** exception error: a system limit has been reached
     in operator  bsl/2
        called as 1 bsl 18446744073709551616

불리언 표현식 (Boolean Expressions)

op Expr
Expr1 op Expr2
Operator Description
not Unary logical NOT
and Logical AND
or Logical OR
xor Logical XOR

Table: Logical Operators.

예시:

1> not true.
false
2> true and false.
false
3> true xor false.
true
4> true or garbage.
** exception error: bad argument
     in operator  or/2
        called as true or garbage

단락 표현식 (Short-Circuit Expressions)

Expr1 orelse Expr2
Expr1 andalso Expr2

Expr2는 필요할 때만 평가됩니다. 즉 Expr2는 다음 경우에만 평가돼요:

  • orelse 표현식에서 Expr1false로 평가되는 경우.

또는

  • andalso 표현식에서 Expr1true로 평가되는 경우.

Expr1의 값(true 또는 false) 또는 (평가된다면) Expr2의 값을 반환합니다.

예시 1:

case A >= -1.0 andalso math:sqrt(A+1) > B of

이것은 A-1.0보다 작아도 동작합니다. 그 경우 math:sqrt/1은 절대 평가되지 않기 때문이에요.

예시 2:

OnlyOne = is_atom(L) orelse
         (is_list(L) andalso length(L) == 1),

Expr2가 불리언 값으로 평가될 필요는 없습니다. 그 덕분에 andalsoorelse는 꼬리 재귀적(tail-recursive)이에요.

예시 3 (꼬리 재귀 함수):

all(Pred, [Hd|Tail]) ->
    Pred(Hd) andalso all(Pred, Tail);
all(_, []) ->
    true.

Change {: .info }

Erlang/OTP R13A 이전에는 Expr2가 불리언 값으로 평가되어야 했고, 그 결과 andalsoorelse는 꼬리 재귀적이 아니었어요.

리스트 연산 (List Operations)

Expr1 ++ Expr2
Expr1 -- Expr2

리스트 연결 연산자 ++는 두 번째 인자를 첫 번째 인자에 붙여 결과 리스트를 반환합니다.

리스트 뺄셈 연산자 --는 첫 번째 인자의 복사본인 리스트를 만듭니다. 절차는 다음과 같아요: 두 번째 인자의 각 요소에 대해, 그 요소의 (있다면) 첫 번째 출현이 제거됩니다.

예시:

1> [1,2,3] ++ [4,5].
[1,2,3,4,5]
2> [1,2,3,2,1,2] -- [2,1,2].
[3,1,2]

맵 표현식 (Map Expressions)

맵 생성 (Creating Maps)

새 맵을 구성하는 것은 표현식 K를 다른 표현식 V와 연관시키는 것입니다:

#{K => V}

새 맵은 각 연관을 나열해서 생성 시 여러 연관을 포함할 수 있어요:

#{K1 => V1, ..., Kn => Vn}

어떤 용어도 서로 연관시키지 않으면 빈 맵이 구성됩니다:

#{}

맵의 모든 키와 값은 용어입니다. 어떤 표현식이든 먼저 평가된 다음, 그 결과 용어가 각각 _키_와 _값_으로 사용됩니다.

키와 값은 => 화살표로 구분되고, 연관은 쉼표(,)로 구분됩니다.

예시:

M0 = #{},                 % empty map
M1 = #{a => <<"hello">>}, % single association with literals
M2 = #{1 => 2, b => b},   % multiple associations with literals
M3 = #{k => {A,B}},       % single association with variables
M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

여기서 AB는 임의의 표현식이고, M0부터 M4는 결과 맵 용어들입니다.

두 개의 매칭되는 키가 선언되면 나중의 키가 우선합니다.

예시:

1> #{1 => a, 1 => b}.
#{1 => b }
2> #{1.0 => a, 1 => b}.
#{1 => b, 1.0 => a}

키(및 그 연관 값)를 구성하는 표현식들이 평가되는 순서는 정의되어 있지 않습니다. 생성에서 키-값 쌍의 문법적 순서는 방금 언급한 두 매칭 키의 경우를 제외하고는 관련이 없어요.

맵 갱신 (Updating Maps)

맵을 갱신하는 것은 구성하는 것과 비슷한 문법을 가집니다.

갱신될 맵을 정의하는 표현식이, 갱신될 키와 각각의 값을 정의하는 표현식 앞에 놓입니다:

M#{K => V}

여기서 M은 map 타입의 용어이고, KV는 어떤 표현식이든 될 수 있어요.

K가 맵의 기존 키와 매칭되지 않으면 키 K에서 값 V로 새 연관이 생성됩니다.

K가 맵 M의 기존 키와 매칭되면 그 연관 값이 새 값 V로 바뀝니다. 두 경우 모두 평가된 맵 표현식은 새 맵을 반환합니다.

M이 map 타입이 아니면 badmap 타입의 예외가 발생합니다.

기존 값을 갱신만 하려면 다음 문법을 사용합니다:

M#{K := V}

여기서 M은 map 타입의 용어, V는 표현식, KM의 기존 키로 평가되는 표현식입니다.

K가 맵 M의 기존 키와 매칭되지 않으면 런타임에 badkey 타입의 예외가 발생합니다. 매칭되는 키 K가 맵 M에 있으면 그 연관 값이 새 값 V로 바뀌고, 평가된 맵 표현식은 새 맵을 반환합니다.

M이 map 타입이 아니면 badmap 타입의 예외가 발생합니다.

예시:

M0 = #{},
M1 = M0#{a => 0},
M2 = M1#{a => 1, b => 2},
M3 = M2#{"function" => fun() -> f() end},
M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' were added in `M1` and `M2`.

여기서 M0은 어떤 맵이든 됩니다. 따라서 M1부터 M4도 맵이라는 결과가 나와요.

더 많은 예시:

1> M = #{1 => a}.
#{1 => a }
2> M#{1.0 => b}.
#{1 => a, 1.0 => b}.
3> M#{1 := b}.
#{1 => b}
4> M#{1.0 := b}.
** exception error: bad argument

생성에서와 마찬가지로 키와 값 표현식이 평가되는 순서는 정의되어 있지 않습니다. 갱신에서 키-값 쌍의 문법적 순서는 두 키가 매칭되는 경우를 제외하고는 관련이 없어요. 그 경우 나중의 값이 사용됩니다.

패턴 속의 맵 (Maps in Patterns)

맵의 키-값 연관 매칭은 다음과 같이 합니다:

#{K := V} = M

여기서 M은 어떤 맵이든 돼요. 키 K는 모든 변수가 이미 바인딩된 가드 표현식이어야 합니다. V는 바인딩되거나 바인딩되지 않은 변수를 가진 어떤 패턴이든 될 수 있습니다.

변수 V가 바인딩되지 않았다면, 맵 M에 반드시 존재해야 하는 키 K에 연관된 값에 바인딩됩니다. 변수 V가 바인딩되었다면 M에서 K에 연관된 값과 매칭되어야 해요.

Change {: .info }

Erlang/OTP 23 이전에는 키 K를 정의하는 표현식이 단일 변수나 리터럴 중 하나로 제한됐습니다.

예시:

1> M = #{"tuple" => {1,2}}.
#{"tuple" => {1,2}}
2> #{"tuple" := {1,B}} = M.
#{"tuple" => {1,2}}
3> B.
2.

이것은 변수 B를 정수 2에 바인딩합니다.

마찬가지로 맵에서 여러 값을 매칭할 수 있습니다:

#{K1 := V1, ..., Kn := Vn} = M

여기서 키 K1부터 Kn은 리터럴 또는 바인딩된 변수를 가진 어떤 표현식이든 됩니다. 모든 키 표현식이 성공적으로 평가되고 모든 키가 맵 M에 존재하면, V1 .. Vn의 모든 변수가 각각의 키의 연관 값에 매칭됩니다.

매칭 조건이 충족되지 않으면 매칭은 실패합니다.

맵을 매칭할 때는 연관 구분자로 := 연산자만(=>는 아님) 허용된다는 점을 주의하세요.

매칭에서 키가 선언된 순서는 관련이 없습니다.

매칭에서는 중복 키가 허용되며, 키와 연관된 각 패턴을 매칭합니다:

#{K := V1, K := V2} = M

빈 맵 리터럴(#{})은 패턴으로 쓰이면 어떤 맵이든 매칭합니다:

#{} = Expr

이 표현식은 Expr이 map 타입이면 매칭되고, 그렇지 않으면 badmatch 예외로 실패합니다.

다음은 가져올 키가 표현식으로 만들어지는 경우입니다:

#{{tag,length(List)} := V} = Map

List는 이미 바인딩된 변수여야 합니다.

매칭 문법 (Matching Syntax)

함수 헤드에서는 리터럴을 키로 매칭하는 것이 허용됩니다:

%% only start if not_started
handle_call(start, From, #{state := not_started} = S) ->
...
    {reply, ok, S#{state := start}};

%% only change if started
handle_call(change, From, #{state := start} = S) ->
...
    {reply, ok, S#{state := changed}};

가드 속의 맵 (Maps in Guards)

모든 하위 표현식이 유효한 가드 표현식이기만 하면 맵은 가드 안에서 허용됩니다.

다음 가드 BIF들이 맵을 다룹니다:

비트 문법 표현식 (Bit Syntax Expressions)

비트 문법은 _비트 문자열(bit string)_에 대해 동작합니다. 비트 문자열은 최상위 비트부터 최하위 비트 순서로 정렬된 비트의 연속입니다.

<<>>  % The empty bit string, zero length
<<E1>>
<<E1,...,En>>

각 요소 Ei는 비트 문자열의 _세그먼트(segment)_를 지정합니다. 세그먼트들은 비트 문자열의 최상위 비트부터 최하위 비트까지 왼쪽에서 오른쪽으로 정렬됩니다.

각 세그먼트 스펙 Ei는 기본 타입이 integer인 값 뒤에 선택적 _크기 표현식(size expression)_과 선택적 _타입 지정자 목록(type specifier list)_이 따라오는 형태예요.

Ei = Value |
     Value:Size |
     Value/TypeSpecifierList |
     Value:Size/TypeSpecifierList

비트 문자열 생성에 사용될 때 Value는 정수, float, 또는 비트 문자열로 평가될 표현식입니다. 표현식이 단일 리터럴이나 변수가 아니면 괄호로 감싸야 해요.

비트 문자열 매칭에 사용될 때 Value는 변수, 또는 정수·float·문자열이어야 합니다.

예를 들어 <<"abc">>처럼 문자열 리터럴을 쓰는 것은 <<$a,$b,$c>>의 문법적 설탕이라는 점에 주의하세요.

비트 문자열 생성에 사용될 때 Size는 정수로 평가될 표현식입니다.

비트 문자열 매칭에 사용될 때 Size는 정수로 평가되는 가드 표현식이어야 합니다. 가드 표현식의 모든 변수는 이미 바인딩되어 있어야 해요.

Change {: .info }

Erlang/OTP 23 이전에는 Size가 정수 또는 정수에 바인딩된 변수로 제한됐습니다.

Size 값은 단위(아래 참조)로 세그먼트의 크기를 지정합니다. 기본 값은 타입에 따라 달라요(아래 참조):

  • integer의 경우 8.
  • float의 경우 64.
  • binarybitstring의 경우 전체 바이너리 또는 비트 문자열.

매칭에서 binary나 bitstring 세그먼트의 기본 값은 마지막 요소에만 유효합니다. 매칭의 다른 모든 bit string 또는 binary 요소는 크기 스펙을 가져야 합니다.

바이너리 (Binaries)

길이가 8비트의 배수인 비트 문자열을 _바이너리(binary)_라고 하며, 이것이 가장 흔하고 유용한 비트 문자열 타입입니다.

바이너리는 메모리에 정규(표준) 표현을 가집니다. 다음은 각 바이트의 값이 그 일련 번호인 바이트들의 연속입니다:

<<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

비트 문자열은 바이너리의 후기 일반화이므로, 바이너리에 대한 많은 글과 정보가 비트 문자열에도 그대로 적용됩니다.

예시:

1> <<A/binary, B/binary>> = <<"abcde">>.
* 1:3: a binary field without size is only allowed at the end of a binary pattern
2> <<A:3/binary, B/binary>> = <<"abcde">>.
<<"abcde">>
3> A.
<<"abc">>
4> B.
<<"de">>

utf8, utf16, utf32 타입에서는 Size를 주면 안 됩니다. 세그먼트의 크기는 타입과 값 자체에 의해 암묵적으로 결정됩니다.

TypeSpecifierList는 임의의 순서로 하이픈(-)으로 구분된 타입 지정자들의 목록입니다. 생략된 타입 지정자에는 기본 값이 사용됩니다.

  • Type= integer | float | binary | bytes | bitstring | bits | utf8 | utf16 | utf32 - 기본 값은 integer입니다. bytesbinary의, bitsbitstring의 줄임말이에요. utf 타입에 대한 자세한 정보는 아래를 참고하세요.

  • Signedness= signed | unsigned - 매칭과 타입이 integer일 때만 중요합니다. 기본 값은 unsigned입니다.

  • Endianness= big | little | native - 바이트 레벨(옥텟 레벨) 엔디언(바이트 순서)을 지정합니다. native-endian은 엔디언이 로드 시점에, 에를랭 머신이 실행되는 CPU에 네이티브인 것이 big-endian이냐 little-endian이냐에 따라 big-endian 또는 little-endian으로 결정된다는 뜻이에요. 엔디언은 Typeinteger, utf16, utf32, float 중 하나일 때만 중요합니다. 기본 값은 big입니다.

    <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
    
  • Unit= unit:IntegerLiteral - 허용 범위는 1부터 256입니다. integer, float, bitstring에서는 1, binary에서는 8로 기본 설정됩니다. bitstring, bits, bytes 타입에서는 기본 값과 다른 단위 값을 지정하는 것이 허용되지 않아요. utf8, utf16, utf32 타입에는 어떤 단위 지정자도 주어서는 안 됩니다.

정수 세그먼트 (Integer segments)

Size 값에 단위를 곱한 것이 세그먼트의 비트 단위 크기를 줍니다.

비트 문자열을 생성할 때 정수 세그먼트의 크기 N이 주어진 정수를 담기 너무 작으면, 정수의 최상위 비트들이 조용히 버려지고 오직 N개의 최하위 비트만 비트 문자열에 들어갑니다. 예를 들어 <<16#ff:4>>는 비트 문자열 <<15:4>>를 만듭니다.

float 세그먼트 (Float segments)

Size 값에 단위를 곱한 것이 세그먼트의 비트 단위 크기를 줍니다. float 세그먼트의 비트 단위 크기는 16, 32, 64 중 하나여야 합니다.

비트 문자열을 생성할 때 float 세그먼트의 크기가 주어진 float 값의 표현을 담기 너무 작으면 예외가 발생합니다.

비트 문자열을 매칭할 때 세그먼트의 비트들이 유한한 부동소수점 값의 표현을 담고 있지 않으면 float 세그먼트의 매칭은 실패합니다.

바이너리 세그먼트 (Binary segments)

이 섹션에서 "바이너리 세그먼트(binary segment)"라는 말은 세그먼트 타입 binary, bitstring, bytes, bits 중 어느 하나를 가리킵니다.

Binaries에 관한 단락도 참고하세요.

바이너리를 생성할 때 바이너리 세그먼트에 크기가 지정되지 않으면 전체 바이너리 값이 생성되는 바이너리에 삽입됩니다. 다만 삽입되는 바이너리의 비트 단위 크기가 그 세그먼트의 단위 값으로 균등하게 나누어져야 합니다. 그렇지 않으면 예외가 발생해요.

예를 들어 다음 예시들은 모두 성공합니다:

1> <<(<<"abc">>)/bitstring>>.
<<"abc">>
2> <<(<<"abc">>)/binary-unit:1>>.
<<"abc">>
3> <<(<<"abc">>)/binary>>.
<<"abc">>

처음 두 예시는 세그먼트의 단위 값이 1이고, 세 번째 세그먼트는 단위 값이 8입니다.

크기 1의 비트 문자열을 단위 8(binary의 기본 단위)인 바이너리 세그먼트에 삽입하려는 시도는 이 예시에서처럼 실패합니다:

1> <<(<<1:1>>)/binary>>.
** exception error: bad argument

생성이 성공하려면 세그먼트의 단위 값이 1이어야 해요:

2> <<(<<1:1>>)/bitstring>>.
<<1:1>>
3> <<(<<1:1>>)/binary-unit:1>>.
<<1:1>>

마찬가지로 크기가 지정되지 않은 바이너리 세그먼트를 매칭할 때, 나머지 바이너리의 비트 단위 크기가 단위 값으로 균등하게 나누어질 때에만 매칭이 성공합니다:

1> <<_/binary-unit:16>> = <<"">>.
<<>>
2> <<_/binary-unit:16>> = <<"a">>.
** exception error: no match of right hand side value <<"a">>
3> <<_/binary-unit:16>> = <<"ab">>.
<<"ab">>
4> <<_/binary-unit:16>> = <<"abc">>.
** exception error: no match of right hand side value <<"abc">>
5> <<_/binary-unit:16>> = <<"abcd">>.
<<"abcd">>

바이너리 세그먼트에 크기가 명시적으로 지정되면, 세그먼트의 비트 단위 크기는 Size 값에 기본 또는 명시적 단위 값을 곱한 것입니다.

바이너리를 생성할 때 삽입되는 바이너리의 크기는 바이너리 세그먼트의 크기보다 적어도 커야 합니다.

예시:

1> <<(<<"abc">>):2/binary>>.
<<"ab">>
2> <<(<<"a">>):2/binary>>.
** exception error: construction of binary failed
        *** segment 1 of type 'binary': the value <<"a">> is shorter than the size of the segment

유니코드 세그먼트 (Unicode segments)

타입 utf8, utf16, utf32는 각각 Unicode Transformation Format UTF-8, UTF-16, UTF-32의 인코딩/디코딩을 지정합니다.

utf 타입의 세그먼트를 생성할 때 Value0부터 16#D7FF 또는 16#E000부터 16#10FFFF 범위의 정수여야 합니다. Value가 허용 범위 밖이면 badarg 예외로 생성이 실패합니다. 인코딩된 값들의 크기는 다음과 같습니다:

  • utf8의 경우 Value는 1-4바이트로 인코딩됩니다.
  • utf16의 경우 Value는 2 또는 4바이트로 인코딩됩니다.
  • utf32의 경우 Value는 4바이트로 인코딩됩니다.

생성할 때 UTF 타입 중 하나 뒤에 리터럴 문자열을 줄 수 있는데, 예를 들어 <<"abc"/utf8>><<$a/utf8,$b/utf8,$c/utf8>>의 문법적 설탕입니다.

utf 타입의 세그먼트 매칭이 성공하면 0부터 16#D7FF 또는 16#E000부터 16#10FFFF 범위의 정수가 결과입니다. 반환된 값이 그 범위 밖이면 매칭은 실패합니다.

utf8 타입의 세그먼트는 매칭 위치의 비트 문자열에 유효한 UTF-8 시퀀스가 있으면 그 비트 문자열에서 1-4바이트를 매칭합니다. (RFC-3629 또는 Unicode 표준을 참고하세요.)

utf16 타입의 세그먼트는 비트 문자열에서 2 또는 4바이트를 매칭할 수 있습니다. 매칭 위치의 비트 문자열에 유니코드 코드포인트의 합법적인 UTF-16 인코딩이 없으면 매칭은 실패합니다. (RFC-2781 또는 Unicode 표준을 참고하세요.)

utf32 타입의 세그먼트는 integer 세그먼트가 32비트를 매칭하는 것과 같은 방식으로 비트 문자열에서 4바이트를 매칭할 수 있습니다. 결과 정수가 앞서 언급한 합법적 범위 밖이면 매칭은 실패합니다.

예시:

1> Bin1 = <<1,17,42>>.
<<1,17,42>>
2> Bin2 = <<"abc">>.
<<97,98,99>>

3> Bin3 = <<1,17,42:16>>.
<<1,17,0,42>>
4> <<A,B,C:16>> = <<1,17,42:16>>.
<<1,17,0,42>>
5> C.
42
6> <<D:16,E,F>> = <<1,17,42:16>>.
<<1,17,0,42>>
7> D.
273
8> F.
42
9> <<G,H/binary>> = <<1,17,42:16>>.
<<1,17,0,42>>
10> H.
<<17,0,42>>
11> <<G,J/bitstring>> = <<1,17,42:12>>.
<<1,17,2,10:4>>
12> J.
<<17,2,10:4>>

13> <<1024/utf8>>.
<<208,128>>

14> <<1:1,0:7>>.
<<128>>
15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>.
<<35,1:4>>

비트 문자열 패턴은 중첩될 수 없음에 주의하세요.

또한 "B=<<1>>"은 "B =< <1>>"으로 해석되어 문법 오류라는 점에 주의하세요. 올바른 방법은 = 뒤에 공백을 쓰는 것입니다: B = <<1>>.

더 많은 예시는 Programming Examples에 있습니다.

펀 표현식 (Fun Expressions)

fun
    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
              Body1;
    ...;
    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
              BodyK
end

펀 표현식은 키워드 fun으로 시작해 키워드 end로 끝납니다. 그 사이에는 일반 함수 선언과 비슷한 함수 선언이 있어야 하는데, 함수 이름이 선택적이고 있다면 변수여야 한다는 점이 다릅니다.

펀 헤드의 변수들은 함수 이름을 가리고, 둘 다 펀 표현식을 둘러싼 함수 절의 변수들을 가립니다. 펀 본문에서 바인딩된 변수는 펀 본문에 지역적입니다.

표현식의 반환 값은 결과 펀입니다.

예시:

1> Fun1 = fun (X) -> X+1 end.
#Fun<erl_eval.6.39074546>
2> Fun1(2).
3
3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
#Fun<erl_eval.6.39074546>
4> Fun2(7).
gt
5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
#Fun<erl_eval.6.39074546>
6> Fun3(4).
24

다음 펀 표현식들도 허용됩니다:

fun Name/Arity
fun Module:Name/Arity

Name/Arity에서 Name은 원자이고 Arity는 정수입니다. Name/Arity는 존재하는 로컬 함수를 지정해야 합니다. 이 표현식은 다음의 문법적 설탕입니다:

fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

Module:Name/Arity에서 ModuleName은 원자이고 Arity는 정수입니다. Module, Name, Arity는 변수일 수도 있어요. 이렇게 정의된 펀은 모듈 Module최신 버전에 있는 arity Arity의 함수 Name을 가리킵니다. 이렇게 정의된 펀은 그 펀이 정의된 모듈의 코드에 의존하지 않습니다.

Change {: .info }

Erlang/OTP R15 이전에는 Module, Name, Arity가 변수인 것이 허용되지 않았어요.

더 많은 예시는 Programming Examples에 있습니다.

Catch와 Throw

catch Expr

값을 반환하기 전에 예외던져지지 않는 한 Expr의 값을 반환하며, 그 경우 그 예외는 잡힙니다. 반환 값은 예외의 클래스에 따라 다릅니다:

  • error (런타임 오류 또는 코드가 error(Term) 호출) - {'EXIT',{Reason,Stack}}가 반환됩니다.

  • exit (코드가 exit(Term) 호출) - {'EXIT',Term}이 반환됩니다.

  • throw (코드가 throw(Term) 호출) - Term이 반환됩니다.

Reason은 발생한 오류의 타입에 따라 달라지고, Stack은 최근 함수 호출 스택입니다. Exit Reasons를 참고하세요.

예시:

1> catch 1+2.
3
2> catch 1+a.
{'EXIT',{badarith,[...]}}

throw(Any) BIF는 함수에서 비국소적(non-local) 반환에 사용될 수 있습니다. 값 Any를 반환하는 catch 안에서 평가되어야 합니다.

예시:

3> catch throw(hello).
hello

throw/1이 catch 안에서 평가되지 않으면 nocatch 런타임 오류가 발생합니다.

Change {: .info }

Erlang/OTP 24 이전에는 catch 연산자가 가장 낮은 우선순위를 가져서, 매칭 연산자와 결합할 때 괄호를 추가해야 했어요:

1> A = (catch 42).
42
2> A.
42

Erlang/OTP 24부터는 괄호를 생략할 수 있습니다:

1> A = catch 42.
42
2> A.
42

Try

try Exprs
catch
    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
        ExceptionBody1;
    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
        ExceptionBodyN
end

이것은 catch의 개선입니다. 다음을 가능하게 해줘요:

  • 서로 다른 예외 클래스를 구별한다.
  • 원하는 것만 처리하도록 고른다.
  • 나머지는 둘러싼 trycatch, 또는 기본 오류 처리로 넘긴다.

try 표현식에서 키워드 catch가 사용되지만, try 표현식 안에는 catch 표현식이 없다는 점에 주의하세요.

Exprs(표현식 시퀀스 Expr1, ..., ExprN)의 값이, 평가 중에 예외가 발생하지 않는 한 반환됩니다. 그 경우 예외가 잡히고, 올바른 예외 클래스 Class를 가진 패턴 ExceptionPattern이 잡힌 예외에 순차적으로 매칭됩니다. 매칭이 성공하고 선택적 가드 시퀀스 ExceptionGuardSeq가 true이면 그에 대응하는 ExceptionBody가 평가되어 반환 값이 됩니다.

Stacktrace는 지정된다면 (패턴이 아니라) 변수의 이름이어야 합니다. 해당 ExceptionPattern이 매칭되면 스택 트레이스가 그 변수에 바인딩됩니다.

Exprs 평가 중에 예외가 발생했는데 올바른 Class의 매칭 ExceptionPattern이 true인 가드 시퀀스로 없으면, Exprstry 표현식에 감싸이지 않은 것처럼 예외가 전달됩니다.

ExceptionBody 평가 중에 예외가 발생하면 그것은 잡히지 않습니다.

ClassStacktrace를 생략하는 것은 허용됩니다. 생략된 Classthrow의 줄임말입니다:

try Exprs
catch
    ExceptionPattern1 [when ExceptionGuardSeq1] ->
        ExceptionBody1;
    ExceptionPatternN [when ExceptionGuardSeqN] ->
        ExceptionBodyN
end

try 표현식은 of 섹션을 가질 수 있습니다:

try Exprs of
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
catch
    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
        ExceptionBody1;
    ...;
    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
        ExceptionBodyN
end

Exprs의 평가가 예외 없이 성공하면 패턴 Patterncase 표현식에서와 같은 방식으로 결과에 순차적으로 매칭됩니다. 매칭이 실패하면 case_clause 대신 try_clause 런타임 오류가 발생한다는 점만 달라요.

Exprs 평가 중에 발생한 예외만 catch 섹션이 잡을 수 있습니다. Body에서 또는 실패한 매칭으로 인해 발생한 예외는 잡히지 않습니다.

try 표현식은 부수 효과가 있는 정리(cleanup)에 쓰기 위한 after 섹션으로도 확장할 수 있습니다:

try Exprs of
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
catch
    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
        ExceptionBody1;
    ...;
    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
        ExceptionBodyN
after
    AfterBody
end

AfterBodyBody 또는 ExceptionBody 중 어느 쪽이 평가되든 그 후에 평가됩니다. AfterBody의 평가된 값은 버려져요. after 섹션이 있는 try 표현식의 반환 값은 없는 것과 같습니다.

Body 또는 ExceptionBody 평가 중에 예외가 발생해도 AfterBody는 평가됩니다. 이 경우 AfterBody가 평가된 후 예외가 전달되므로, after 섹션이 있는 try 표현식의 예외는 없는 것과 같습니다.

AfterBody 자체 평가 중에 예외가 발생하면 그것은 잡히지 않습니다. 그래서 AfterBodyExprs, Body, ExceptionBody의 예외 후에 평가되면, 그 예외는 잃어버리고 AfterBody의 예외에 가려집니다.

of, catch, after 섹션은 모두 선택적이며, 최소한 catchafter 섹션 하나가 있으면 됩니다. 따라서 다음은 모두 유효한 try 표현식입니다:

try Exprs of
    Pattern when GuardSeq ->
        Body
after
    AfterBody
end

try Exprs
catch
    ExceptionPattern ->
        ExceptionBody
after
    AfterBody
end

try Exprs after AfterBody end

다음은 after를 사용하는 예시입니다. file:read/2binary_to_term/1에서 예외가 발생해도 파일을 닫습니다. 예외는 try...after...end 표현식이 없는 것과 같습니다:

termize_file(Name) ->
    {ok,F} = file:open(Name, [read,binary]),
    try
        {ok,Bin} = file:read(F, 1024*1024),
        binary_to_term(Bin)
    after
        file:close(F)
    end.

다음은 trycatch Expr을 흉내 내는 예시입니다:

try Expr
catch
    throw:Term -> Term;
    exit:Reason -> {'EXIT',Reason};
    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
end

이들 표현식의 여러 부분에서 바인딩된 변수는 서로 다른 스코프를 가집니다. try 키워드 바로 뒤에서 바인딩된 변수는:

  • of 섹션에서 바인딩됨
  • catchafter 섹션 둘 다, 그리고 전체 구성 뒤에서 unsafe

of 섹션에서 바인딩된 변수는:

  • catch 섹션에서 바인딩되지 않음
  • after 섹션, 그리고 전체 구성 뒤에서 unsafe

catch 섹션에서 바인딩된 변수는 after 섹션과 전체 구성 뒤에서 unsafe합니다.

after 섹션에서 바인딩된 변수는 전체 구성 뒤에서 unsafe합니다.

괄호 표현식 (Parenthesized Expressions)

(Expr)

괄호 표현식은 연산자 우선순위를 재정의하는 데 유용합니다. 예를 들어 산술 표현식에서요:

1> 1 + 2 * 3.
7
2> (1 + 2) * 3.
9

블록 표현식 (Block Expressions)

begin
   Expr1,
   ...,
   ExprN
end

블록 표현식은 절 본문과 비슷하게 일련의 표현식을 묶는 방법을 제공합니다. 반환 값은 마지막 표현식 ExprN의 값입니다.

컴프리헨션 (Comprehensions)

컴프리헨션(comprehension)은 하나 이상의 용어를 반복하고 새 용어를 구성하는 간결한 표기를 제공합니다. 컴프리헨션은 만들어내는 용어의 타입에 따라 세 가지 종류가 있어요.

리스트 컴프리헨션은 리스트를 구성합니다. 다음 문법을 가집니다:

[Expr || Qualifier1, . . ., QualifierN]

여기서 Expr은 임의의 표현식이고, 각 Qualifier생성자(generator) 또는 필터(filter) 예요.

비트 문자열 컴프리헨션은 비트 문자열 또는 바이너리를 구성합니다. 다음 문법을 가집니다:

<< BitStringExpr || Qualifier1, . . ., QualifierN >>

BitStringExpr은 비트 문자열로 평가되는 표현식입니다. BitStringExpr이 함수 호출이면 괄호로 감싸야 해요. 각 Qualifier생성자 또는 필터입니다.

맵 컴프리헨션은 맵을 구성합니다. 다음 문법을 가집니다:

#{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

여기서 KeyExprValueExpr은 임의의 표현식이고, 각 Qualifier생성자 또는 필터입니다.

Change {: .info }

맵 컴프리헨션과 맵 생성자는 Erlang/OTP 26에서 도입됐어요.

생성자에는 네 가지 종류가 있습니다. 그중 세 가지는 완화(relaxed) 변형과 엄격(strict) 변형을 가집니다. 네 번째 종류인 zip 생성자는 두 개 이상의 non-zip 생성자로 구성됩니다.

Change {: .info }

엄격 생성자와 zip 생성자는 Erlang/OTP 28에서 도입됐어요. 엄격 또는 완화 생성자가 모두 동작할 때는 엄격 생성자를 쓰는 것이 더 좋은 관행입니다. 자세한 내용은 Programming Examples에 있습니다.

_리스트 생성자_는 완화 변형에 대해 다음 문법을 가집니다:

Pattern <- ListExpr

그리고 엄격 변형:

Pattern <:- ListExpr

여기서 ListExpr은 용어 리스트로 평가되는 표현식입니다.

_비트 문자열 생성자_는 완화 변형에 대해 다음 문법을 가집니다:

BitstringPattern <= BitStringExpr

그리고 엄격 변형:

BitstringPattern <:= BitStringExpr

여기서 BitStringExpr은 비트 문자열로 평가되는 표현식입니다.

_맵 생성자_는 완화 변형에 대해 다음 문법을 가집니다:

KeyPattern := ValuePattern <- MapExpression

그리고 엄격 변형:

KeyPattern := ValuePattern <:- MapExpression

여기서 MapExpression은 맵 또는 maps:iterator/1이나 maps:iterator/2를 호출해 얻은 맵 이터레이터로 평가되는 표현식입니다.

_zip 생성자_는 다음 문법을 가집니다:

Generator_1 && ... && Generator_n

여기서 각 Generator_i는 non-zip 생성자입니다. zip 생성자 안의 생성자들은 하나의 생성자로 취급되어 병렬로 평가됩니다.

_필터_는 true 또는 false로 평가되는 표현식입니다.

생성자 패턴의 변수들은 이전에 바인딩된 변수들을 가립니다. 여기에는 이전 생성자 패턴에서 바인딩된 변수도 포함돼요.

생성자 표현식에서 바인딩된 변수는 그 표현식 밖에서는 보이지 않습니다:

1> [{E,L} || E <- L=[1,2,3]].
* 1:5: variable 'L' is unbound

리스트 컴프리헨션은 리스트를 반환합니다. 리스트 요소는 모든 필터가 true인 생성자 요소들의 각 조합에 대해 Expr을 평가한 결과입니다.

비트 문자열 컴프리헨션은 비트 문자열을 반환합니다. 그것은 모든 필터가 true인 비트 문자열 생성자 요소들의 각 조합에 대해 BitStringExpr을 평가한 결과들을 연결해 만들어집니다.

맵 컴프리헨션은 맵을 반환합니다. 맵 요소는 모든 필터가 true인 생성자 요소들의 각 조합에 대해 KeyExprValueExpr을 평가한 결과입니다. 키 표현식이 유일하지 않으면 마지막 출현이 맵에 저장됩니다.

예시:

리스트의 각 요소에 2를 곱하기:

1> [X*2 || X <:- [1,2,3]].
[2,4,6]

바이너리의 각 바이트에 2를 곱해서 리스트 반환:

1> [X*2 || <<X>> <:= <<1,2,3>>].
[2,4,6]

바이너리의 각 바이트에 2를 곱하기:

1> << <<(X*2)>> || <<X>> <:= <<1,2,3>> >>.
<<2,4,6>>

리스트의 각 요소에 2를 곱해서 바이너리 반환:

1> << <<(X*2)>> || X <:- [1,2,3] >>.
<<2,4,6>>

정수에서 그 제곱으로의 매핑 만들기:

1> #{X => X*X || X <:- [1,2,3]}.
#{1 => 1,2 => 4,3 => 9}

맵의 각 요소 값에 2를 곱하기:

1> #{K => 2*V || K := V <:- #{a => 1,b => 2,c => 3}}.
#{a => 2,b => 4,c => 6}

홀수만 남기고 리스트 필터링:

1> [X || X <:- [1,2,3,4,5], X rem 2 =:= 1].
[1,3,5]

매칭되는 요소만 남기고 리스트 필터링:

1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
[{a,b},{1,2}]

요소가 2-튜플이 아니면 크래시하며 리스트 필터링:

1> [X || {_,_}=X <:- [{a,b}, [a], {x,y,z}, {1,2}]].
** exception error: no match of right hand side value [a]

두 리스트 생성자의 요소 결합:

1> [{P,Q} || P <:- [a,b,c], Q <:- [1,2]].
[{a,1},{a,2},{b,1},{b,2},{c,1},{c,2}]

zip 생성자를 사용해 두 리스트 생성자의 요소 결합:

1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3]].
[{a,1},{b,2},{c,3}]

zip 생성자를 사용해 두 리스트 생성자의 요소를 결합하고 홀수 걸러내기:

1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3], Q rem 2 =:= 0].
[{b,2}]

두 리스트에서 매칭되지 않는 요소 걸러내기.

1> [X || X <- [1,2,3,5] && X <- [1,4,3,6]].
[1,3]

더 많은 예시는 Programming Examples에 있습니다.

생성자가 없으면 컴프리헨션은, 모든 필터가 true이면 단일 요소(그 Expr을 평가한 결과)로 구성된 용어를, 그렇지 않으면 요소가 없는 용어(리스트 컴프리헨션은 [], 비트 문자열 컴프리헨션은 <<>>, 맵 컴프리헨션은 #{})를 반환합니다.

예시:

1> [2 || is_integer(2)].
[2]
2> [x || is_integer(x)].
[]

필터 표현식이 불리언 값으로 평가되지 않을 때 일어나는 일은 그 표현식에 따라 달라집니다:

  • 표현식이 가드 표현식이면, 평가 실패 또는 비불리언 값으로 평가되는 것은 false로 평가되는 것과 동등합니다.
  • 표현식이 가드 표현식이 아니고 비불리언 값 Val으로 평가되면 런타임에 예외 {bad_filter, Val}이 발생합니다. 표현식의 평가가 예외를 일으키면 그것은 컴프리헨션이 잡지 않아요.

예시 (필터로 가드 표현식 사용):

1> List = [1,2,a,b,c,3,4].
[1,2,a,b,c,3,4]
2> [E || E <:- List, E rem 2].
[]
3> [E || E <:- List, E rem 2 =:= 0].
[2,4]

예시 (필터로 non-guard 표현식 사용):

1> List = [1,2,a,b,c,3,4].
[1,2,a,b,c,3,4]
2> FaultyIsEven = fun(E) -> E rem 2 end.
#Fun<erl_eval.42.17316486>
3> [E || E <:- List, FaultyIsEven(E)].
** exception error: bad filter 1
4> IsEven = fun(E) -> E rem 2 =:= 0 end.
#Fun<erl_eval.42.17316486>
5> [E || E <:- List, IsEven(E)].
** exception error: an error occurred when evaluating an arithmetic expression
     in operator  rem/2
        called as a rem 2
6> [E || E <:- List, is_integer(E), IsEven(E)].
[2,4]

가드 시퀀스 (Guard Sequences)

_가드 시퀀스(guard sequence)_는 세미콜론(;)으로 구분된 가드들의 연속입니다. 가드 시퀀스는 가드 중 적어도 하나가 true이면 true예요. (나머지 가드들은, 있다면, 평가되지 않습니다.)

Guard1; ...; GuardK

_가드(guard)_는 쉼표(,)로 구분된 가드 표현식들의 연속입니다. 가드는 모든 가드 표현식이 true로 평가되면 true입니다.

GuardExpr1, ..., GuardExprN

가드 표현식 (Guard Expressions)

유효한 _가드 표현식(guard expression)_의 집합은 유효한 에를랭 표현식 집합의 부분집합입니다. 유효한 표현식 집합을 제한하는 이유는 가드 표현식의 평가가 부수 효과가 없음이 보장되어야 하기 때문이에요. 유효한 가드 표현식은 다음과 같습니다:

  • 변수
  • 상수 (원자, 정수, float, 리스트, 튜플, 레코드, 바이너리, 맵)
  • 원자, 정수, float, 리스트, 튜플, 레코드, 바이너리, 맵을 구성하는 표현식
  • 맵을 갱신하는 표현식
  • 레코드 표현식 Expr#Name.Field#Name.Field
  • Type Test BIFsOther BIFs Allowed in Guard Expressions 에 지정된 BIF 호출
  • 용어 비교
  • 산술 표현식
  • 불리언 표현식
  • 단락 표현식 (andalso/orelse)
BIF
is_atom/1
is_binary/1
is_bitstring/1
is_boolean/1
is_float/1
is_function/1
is_function/2
is_integer/1
is_list/1
is_map/1
is_number/1
is_pid/1
is_port/1
is_record/2
is_record/3
is_reference/1
is_tuple/1

Table: Type Test BIFs

대부분의 타입 검사 BIF에는 is_ 접두어가 없는 더 오래된 동등물이 있다는 점에 주의하세요. 이 옛 BIF들은 하위 호환성 때문에만 유지되며 새 코드에서는 사용해서는 안 됩니다. 또한 최상위에서만 허용됩니다. 예를 들어 가드의 불리언 표현식에서는 허용되지 않아요.

BIF
abs(Number)
bit_size(Bitstring)
byte_size(Bitstring)
element(N, Tuple)
float(Term)
hd(List)
is_map_key(Key, Map)
length(List)
map_get(Key, Map)
map_size(Map)
max(A, B)
min(A, B)
node/0
node(Pid | Ref | Port)
round(Number)
self/0
size(Tuple | Bitstring)
tl(List)
trunc(Number)
tuple_size(Tuple)

Table: Other BIFs Allowed in Guard Expressions

Change {: .info }

min/2max/2 BIF는 Erlang/OTP 26부터 가드에서 사용이 허용됩니다.

산술 표현식, 불리언 표현식, 단락 표현식 또는 가드 BIF 호출이 (잘못된 인자로) 실패하면 전체 가드가 실패합니다. 가드가 가드 시퀀스의 일부였다면 시퀀스의 다음 가드(다음 세미콜론 뒤의 가드)가 평가됩니다.

연산자 우선순위 (Operator Precedence)

내림차순 연산자 우선순위:

Operator Association
#
Unary + - bnot not
/ * div rem band and Left-associative
+ - bor bxor bsl bsr or xor Left-associative
++ -- Right-associative
== /= =< < >= > =:= =/= Non-associative
andalso Left-associative
orelse Left-associative
catch
= ! Right-associative
?= Non-associative

Table: Operator Precedence

Change {: .info }

Erlang/OTP 24 이전에는 catch 연산자가 가장 낮은 우선순위를 가졌어요.

Note {: .info }

표의 = 연산자는 매칭 연산자입니다. 문자 =는 또한 패턴에서만 사용할 수 있는 복합 패턴 연산자를 나타낼 수도 있습니다.

?=maybe 블록 안 최상위에서만 사용할 수 있다는 제한이 있습니다.

표현식을 평가할 때 우선순위가 가장 높은 연산자가 먼저 평가됩니다. 같은 우선순위의 연산자들은 그 연관성(associativity)에 따라 평가됩니다. 비연관(non-associative) 연산자는 같은 우선순위의 연산자와 결합할 수 없습니다.

예시:

왼쪽 연관 산술 연산자는 왼쪽에서 오른쪽으로 평가됩니다:

6 + 5 * 4 - 3 / 2 evaluates to
6 + 20 - 1.5 evaluates to
26 - 1.5 evaluates to
24.5

비연관 연산자는 결합할 수 없습니다:

1> 1 < X < 10.
* 1:7: syntax error before: '<'

더 알아보기 (Learn more)