엘릭서의 산술, 비교, 부울 연산자를 살펴봅니다.
이번에는 프로그래밍 언어를 사용하기 위한 필수 연산자에 대해 배울 것입니다. 많은 언어에서 비슷한 연산자를 배웠기 때문에, 새로운 언어를 배울 때 이 부분은 필수지만 지루한 것 같습니다. 다행히도 여기에는 어려움이나 독특한 것들이 적습니다. 엘릭서만의 몇 가지 독특한 특징들도 있지만, 대부분 이 연산자들은 제가 사용했던 다른 언어들과 비슷합니다.
산술 연산자는 기본적인 수학 연산자입니다.
덧셈, 뺄셈, 곱셈, 나눗셈(+
, -
, *
, /
)이 있습니다. 이 연산자들은 자바스크립트, C# 혹은 제가 사용한 대부분 언어와 같습니다. 다른 수학적 연산자는 없습니다. 자바스크립트와 C#에서 많이 쓰이는 증감 연산자도 없습니다. 다른 수학 연산은 모두 함수를 통해 수행해야 합니다.
IEx에서 몇 가지 예제를 살펴보겠습니다.
iex> 6 + 3
9
iex> 6 - 3
3
iex> 6 * 3
18
iex> 6 / 3
2.0
연산 순서는 제가 본 다른 프로그래밍 언어와 같습니다. 자바스트립트나 C#처럼 괄호를 사용하면 연산 순서를 재정의하거나 그룹화를 하여 연산을 읽기 쉽게 할 수 있습니다.
iex> 4 + 3 * 5
19
iex> (4 + 3) * 5
35
엘릭서에서 나눗셈 연산자(/)를 사용하는 나눗셈은 피연산자가 정수든 부동 소수이든 상관없이 항상 부동 소수가 됩니다. 그래서 5 / 2 는 2.5이고, 8 / 2 는 4.0입니다. 자바스트립트는 비슷하게 동작하지만, 자바스크립트는 단일 숫자 타입을 가지고 있습니다. C#은 이에 해당하지 않습니다. C#에서 두 정수를 나눈 결과는 정수가 됩니다. 엘릭서에서 정수 나눗셈을 하려면 div
함수를 통해 정수 나눗셈을 할 수 있습니다.
iex> 5 / 2
2.5
iex> div(5, 2)
2
iex> 8 / 2
4.0
iex> div(8, 2)
4
엘릭서에는 자바스트립트나 C#처럼 모듈로 연산자가 없습니다. rem
함수를 사용하여 같은 작업을 수행할 수 있습니다.
아래는 C#이나 자바스크립트의 5 % 2
와 28 % 10
에 해당합니다.
iex> rem(5, 2)
1
iex> rem(28, 10)
8
rem/2
함수와 다른 언어의 일반적인 모듈로 연산은 한 가지 차이점이 있습니다. 결과가 0이 아닌 한 결과의 부호는 첫 번째 인자의 부호와 같습니다.
iex> rem(-10, 10)
0
iex> rem(-10, 12)
-10
iex> rem(10, 12)
10
iex> rem(-100, 12)
-4
비교 연산자도 C#과 자바스크립트의 연산자와 매우 유사하지만, 몇 가지 차이점이 있습니다.
자바스크립트와 마찬가지로 엘릭서는 이중 등호(==
)와 삼중 등호(===
) 비교 연산자가 있습니다. 자바스크립트처럼 삼중 등호가 이중 등호보다 엄격하지만, 자바스크립트와는 다르게 엄격함의 차이가 작습니다.
삼중 등호 연산자(===
)는 비교 대상인 두 항목의 유형이 같은지와 유형이 같다면 정확하게 같은 값인지 테스트합니다. 몇 가지 예를 살펴보겠습니다.
iex> "Bob" === 4
false
iex> 4 === 4
true
iex> "Bob" === "Bob"
true
iex> 4.0 === 4
false
iex> true === "true"
false
iex> 3.5 === :cheese
false
iex> :chees === :cheese
false
iex> :cheese === :cheese
true
네, 아톰의 이름이 다르면 완전히 다른 두 개의 아톰이라는 것을 보여주기 위해 철자가 틀린 :chees
를 두었습니다.
이중 등호 연산자 (==
)는 거의 똑같습니다. 차이점은 오직 부동 소수점과 정수를 서로 비교할 수 있도록 강제한다는 점입니다.
iex> 5 === 5.0
false
iex> 5 == 5.0
true
iex> 5 == 5.000000001
false
iex> "true" == true
false
iex> 1 == true
false
iex> 0 == false
false
자바스크립트처럼 모든 규칙을 기억해야 하는 복잡한 일은 없습니다. 부동 소수점과 정수가 같은 수를 나타내는 경우 두 값이 같을 수 있다는 것을 허용하기만 하면 됩니다.
반면에 C#은 정적 타입 언어이므로 정확하게 비교하는 것을 특별히 구현한 메소드가 없는 한 서로 다른 데이터 타입은 비교할 수 없습니다. 이것이 정적 타입 언어와 동적 타입 언어의 차이점입니다.
같지 않은 연산자 !=
의 반대는 ==
가 있고, !==
의 반대는 ===
가 있습니다. 이러한 연산자는 자바스크립트의 동등 연산자와 비슷하게 생겼습니다.
따라서 자바스크립트와 다르게 기대하지 않은 타입 변환을 방지하기 위해 엄격한 동등 연산자를 사용할 필요는 없을 것 같습니다. 부동 소수점과 정수를 구분해야 하는 경우가 아니라면 기본적으로 이중 등호 ==
사용을 추천합니다.
지금까지 살펴본 바로는 C#이나 자바스크립트처럼 같은 데이터의 다른 인스턴스를 비교하는 것에 대해서는 걱정할 필요가 없습니다. C#이나 자바스크립트의 객체는 비교를 수행하는 코드가 포함된 특수한 동등
메서드를 구현하지 않는 한 같은 데이터를 포함하는 다른 객체와 같을 수 없습니다(적어도 C#에서는).
엘릭서에서 이 문제를 구체적으로 다룬 내용이 없어서 몇 가지 실험을 해보았습니다. 실험 결과, 데이터 구조는 항상 같은 데이터 구조와 동등하다는 것을 발견했습니다.
서로 다른 시간에 서로 다른 장소에서 생성된 같은 데이터 구조는 엘릭서 내부에서 완전히 같은 데이터에 대한 참조일 뿐이라고 생각합니다. 이것이 사실이라는 내용을 읽지는 못했지만 엘릭서의 모든 데이터는 불면이므로 완벽하게 말이 될 것입니다. 이 언어는 같은 인스턴스 데이터가 수정되는 것에 대해 걱정할 필요가 없으므로 같은 데이터의 복사본을 여러 개 저장할 필요가 없습니다.
이는 C#에서 문자열이 동작하는 방식과 유사합니다. 사용 중인 모든 문자열이 담긴 테이블이 있고, 동일한 문자열을 참조하는 모든 변수는 동일한 문자열 인스턴스를 가리킵니다. 널리 알려지지는 않았지만 C#의 문자열은 실제로 불변입니다. 모든 문자열 연산은 새로운 문자열 인스턴스를 생성합니다. 이러한 불변성은 동일한 문자열의 인스턴스가 두 개가 되지 않기 때문에 메모리를 절약할 수 있습니다. 문자열뿐만 아니라 모든 데이터에 적용된다는 점을 제외하면 엘릭서의 동작 방식과 같습니다.
C#과 자바스크립트에서와 마찬가지로 숫자를 서로 비교하여 어떤 숫자가 다른 숫자보다 큰지 확인할 수 있습니다. 연산자 또한 동일합니다. >
, >=
, <
, <=
정수와 부동 소수점을 서로 비교할 수도 있습니다.
iex> 4 < 4.0
false
iex> 4 > 4.0
false
iex> 5 > 4.0
true
iex> 5 < 4.0
false
iex> 4.0001 > 4
true
iex> 3 >= 3
true
iex> 3 <= 3
true
엘릭서에서 서로 다른 데이터 타입을 비교할 수 있지만, 대부분 결과는 쓸모가 없습니다.
iex> true < 4
false
iex> 4 < true
true
iex> "Bob" > :ok
true
iex> "Bob" < :ok
false
엘릭서는 서로 다른 데이터 타입의 요소가 포함된 컬렉션을 일관되게 정렬할 수 있습니다. 서로 다른 데이터 타입을 정렬하는 몇 가지 시나리오도 있습니다. 엘릭서 문서에 따르면, 데이터 타입 간의 비교는 항상 다음 규칙을 따릅니다.
number < atom < reference < function < port < pid < tuple < map < list < bitstring
서로 다른 데이터 타입을 비교할 때 데이터 타입의 값은 중요하지 않습니다. 비교 결과를 결정하는 것은 데이터 타입 그 자체입니다.
부울 연산자는 부울 표현식을 만들 때 필요합니다. 엘릭서에는 두 가지 부울 연산자 세트가 있습니다. 등호 연산자와 마찬가지로 더 엄격한 연산자 세트와 덜 엄격한 연산자 세트가 있습니다.
덜 엄격한 연산자를 살펴봅시다. 이 연산자는 자바스크립트, C# 그리고 많은 C 계열 언어와 똑같이 생겼습니다. &&
(and), ||
(or), !
(not) 이 있습니다.
자바스크립트와 마찬가지로 &&
, ||
그리고 !
연산자에는 진실성 개념이 있어서 이러한 부울 연산자를 사용하면 모든 데이터 타입이 자동으로 부울 값 true
처럼 작동하는 "진실"로 변하거나, 부울 값 false
처럼 작동하는 "거짓"으로 변합니다.
엘릭서에서 부울 false
와 nil
(C#과 자바스크립트의 null
)은 거짓에 해당합니다. 그 외에는 모두 참입니다. 이는 0과 빈 문자열(다른 값 중에서도)도 거짓이 될 수 있는 자바스크립트보다 훨씬 간단한 정의입니다. 따라서 자바스크립트의 규칙에 익숙하다면 엘릭서의 규칙에 적응해야 합니다. 부울 값에 대해서 엄격하게 작동하는 C#이나 기타 유사한 언어의 부울 연산자에 익숙하다면 "진실" 값과 "거짓" 값의 개념이 다소 이상하게 느껴질 것입니다.
엘릭서에서 (자바스크립트와 마찬가지로) 부울 연산자 &&
및 ||
가 포함된 표현식은 true
및 false
대신 표현식에 있는 값 중 하나를 반환합니다. !
연산자는 항상 값을 true
또는 false
로 변환합니다.
&&
연산자가 있는 표현식은 첫 번째 값이 거짓이면 첫 번째 값으로 평가되고 첫 번째 값이 참이면 두 번째 값으로 평가됩니다. ||
연산자는 그 반대입니다. 표현식은 첫 번째 값이 거짓이면 두 번째 값으로 평가되고 첫 번째 값이 참이면 첫 번째 값으로 평가됩니다.
이런 방식으로 동작하는 언어를 사용해 본 적이 없다면 이상하게 보일 것이고 자바스크립트의 부울 연산자를 잘 알고 있다면 이를 이해하는 데 유리할 것입니다.
혼란스럽게 들린다면 몇 가지 예를 살펴보는 것이 도움이 될 것입니다. 무슨 일이 일어나고 있는지 설명하기 위해 몇 가지 댓글을 추가했습니다.
#"Bob" is truthy, so the expression evaluates to 1
iex> "Bob" && 1
1
#"Bob" is truthy, so the expression evaluates to false
iex> "Bob" && false
false
#"Bob" is truthy, so the expression evaluates to nil
iex> "Bob" && nil
nil
#1 is truthy, so the expression evaluates to "Bob"
iex> 1 && "Bob"
"Bob"
#false is falsey, so the expression evaluates to false
iex> false && "Bob"
false
#true is truthy, so the expression evaluates to "Bob"
iex> true && "Bob"
"Bob"
#nil is falsey, so the expression evaluates to nil
iex> nil && "Bob"
nil
#"false is falsey, so the expression evaluates to true
iex> false || true
true
#true is truthy, so the expression evaluates to false
iex> true || false
true
#0 is truthy (unlike Javascript), so the expression evaluates to false
iex> !0
false
#1 is truthy, so the expression evaluates to false
iex> !1
false
#false is falsey, so the expression evaluates to true
iex> !false
true
#nil is falsey, so the expression evaluates to true
iex> !nil
true
#"1" is truthy, so the expression evaluates to "1"
iex> "1" || 3
"1"
#true is truthy, so the expression evaluates to 18
iex> true && 18
18
#false is falsey, so the expression evaluates to :pencil
iex> false || :pencil
:pencil
#nil is falsey, so the expression evaluates to "spigot"
iex> nil && "spigot"
nil
#"1" is truthy, so the expression evaluates to false
iex> !"1"
false
이제 좀 더 엄격한 부울 연산자 집합을 살펴보겠습니다. 이러한 연산자는 and
, or
그리고 not
입니다. 이 연산자들과 이전 연산자들의 차이점은 이 연산자들은 부울 값에만 연산하고 다른 것은 연산하지 않는다는 점입니다. 자바나 C#에 익숙한 사람이라면 이 연산자가 더 익숙하게 느껴질 것입니다.
실제로는 첫 번째 인자만 부울 값이어야 합니다. 두 번째 인자는 모든 값이 될 수 있으며, 첫 번째 값이 거짓("or" 연산에서)이거나 첫 번째 값이 참("and" 연산에서)인 경우 표현식은 두 번째 값으로 해석됩니다. 두 번째 값은 표현식에서 어떤 인자가 반환되는지(첫 번째 또는 두 번째)와 관련이 없기 때문에 모든 데이터 타입이 될 수 있으며 첫 번째 값에 의해서만 결정됩니다.
몇 가지 예를 살펴보겠습니다.
iex> true or false
true
iex> false or true
true
iex> true and false
false
iex> false and true
false
iex> false and false
false
iex> true and true
true
iex> true or "3"
true
iex> false or "3"
"3"
iex> "1" or 5
** (BadBooleanError) expected a boolean on left-side of "or", got: "1"
iex> true and 10
10
iex> false and 10
false
iex> not false
true
iex> not true
false
iex> not 0
** (ArgumentError) argument error
:erlang.not(0)
iex> not "Bob"
** (ArgumentError) argument error
:erlang.not("Bob")
and
와 or
의 두 번째 인자를 부울이 아닌 값으로 만들 수 있지만, 엘릭서 커뮤니티에서는 이 연산자들을 부울 값이 예상되는 경우에만 사용하도록 권장합니다.
이진 부울 연산자 두 세트(&&
, ||
, and
, or
) 모두 단락 연산자입니다. 즉, and
연산에 대한 첫 번째 인자가 거짓으로 평가되면 두 번째 인자는 전혀 평가되지 않습니다. or
연산은 첫 번째 인자가 참으로 평가되면 두 번째 인자를 평가하지 않습니다. 이는 자바스크립트 및 C#에서 부울 연산자의 동작에 해당하며, 프로그래머는 이를 유리하게 사용합니다.
is_available?(resource) && do_something_with_resource(resource)
is_available?(resource) || acquire_resource(resource)
첫 번째 줄은 리소스를 사용할 수 있는 경우에만 do_something_with_resource를 호출합니다. 두 번째 줄은 리소스를 사용할 수 없는 경우에만 acquire_resource를 호출합니다.
엘릭서에서 연산자를 재정의하여 다른 작업을 수행할 수 있습니다. 엘릭서가 구문을 분석하여 연산자로 인식할 수 있는 연산자 목록이 있지만, 이 연산자는 아무 작업도 수행하지 않습니다. 사용하지 않는 연산자는 나중에 사용할 수 있다고 생각합니다. 이러한 사용하지 않는 연산자에도 기능을 연결할 수 있습니다.
그러나 엘릭서 커뮤니티에서는 사용 여부와 관계없이 연산자를 재정의하는 것을 권장하지 않습니다. 엘릭서 커뮤니티는 명확하고 읽기 쉬운 코드를 강조하며, 특이한 연산자나 예기치 않은 연산자 동작은 보통 이러한 목표에 반합니다. 올바른 해결책은 일반적으로 동일한 작업을 수행하는 명확한 이름의 함수를 만드는 것입니다.