Code

Elixir – 06: Binaries, strings and char lists

February 15, 2016

author:

Array

Elixir – 06: Binaries, strings and char lists

Elixir Tutorial 시리즈입니다. 거의 대부분은 튜토리얼의 한글 번역에 가깝습니다만, 생략되거나 추가로 주석을 달거나 하는 부분이 많습니다. 원문은 최하단의 링크를 참고하세요.

Elixir – 06: Binaries, strings and char lists

“Basic types”에서 문자열과 타입 검사를 위해서 is_binary/1를 쓴다는 것을 배웠습니다.

iex> string = "hello"
"hello"
iex> is_binary(string)
true

이제 Elixir에서 바이너리가 정확히 뭔지, 문자열과는 어떤 관계인지, 'like this'와 같은 작은 따옴표는 어떤 의미인지 봅시다.

UTF-8 and Unicode

문자열은 UTF-8로 인코딩된 바이너리입니다. 이것이 정확히 어떤 의미인지 이해하려면우선 바이트와 문자 코드의 차이를 이해해야 합니다.

표준 유니코드는 문자 코드에 각각의 수많은 문자들과 대응시킵니다. 예를 들어 a의 문자 코드는 97이고, ł의 문자코드는 322입니다. "hełło"같은 문자열을 저장하려면 우선 이 문자 코드들을 바이트로 변환할 필요가 있습니다. 만약 우리가 하나의 문자 코드에 하나의 바이트만을 사용한다면, ł를 위해 322를 사용할 수 없기 때문에 "hełło"를 저장할 수 없을 겁니다. 1바이트 = 8비트 = 256라는걸 생각하면 자연스럽습니다. 물론 실제 우리는 이 문자열을 출력할 수 있다는 사실은 어떤 방법이 존재한다는 소리겠죠. 여기에서 인코딩이 필요합니다.

Elixir는 문자코드를 바이트로 표현할 때, UTF-8을 기본 인코딩으로 채택하고 있습니다. 이는 다시 말하자면 Elixir의 문자열은 UTF-8이라는 방식으로 문자 코드를 바이트로 변환한 뭉치입니다.

ł와 같은 문자를 표현해야하므로 우리는 적어도 1바이트 이상의 공간이 필요합니다. 이러한 이유로 byte_size/1의 결과가 String.length/1와 다른 경우가 존재합니다.

iex> string = "hełło"
"hełło"
iex> byte_size(string)
7
iex> String.length(string)
5

ps: 만약 윈도우에서 실행한다면 터미널이 UTF-8을 사용하지 않고 있을 수도 있으니 iex를 실행하기 전에 chcp 65001를 입력해주세요.

UTF-8은 h, e, o를 저장하기 위해 1바이트를 사용하지만, ł를 위해서 2바이트를 사용합니다. Elixir에서는 ?를 사용해서 문자 코드를 확인할 수 있습니다.

iex> ?a
97
iex> ?ł
322

특이하게 접두 연산자를. 생각해보면 핀 연산자도 그랬습니다.

아니면 String 모듈에 있는 함수를 사용하여 문자열을 각각의 문자 코드로 쪼갤 수도 있습니다.

iex> String.codepoints("hełło")
["h", "e", "ł", "ł", "o"]

많은 유니코드 연산을 지원하는 Elixir는 문자열을 사용할 때 무척 좋은 경험을 하게 해줄겁니다. 사실 Elixir는 “The string type is broken”의 글에 있는 테스트 중 맨 마지막 것을 제외하고 모두 통과합니다.

하지만 문자열은 그저 일부입니다. 문자열이 바이너리이고, is_binary/1를 사용할 수 있다면 Elixir는 문자열을 처리하기 위한 어떤 타입을 가지고 있을겁니다. 실제로도 그렇구요.

Binaries (그리고 bitstrings)

Elixir에서는 <<>>를 사용해서 바이너리를 정의할 수 있습니다.

iex> <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> byte_size(<<0, 1, 2, 3>>)
4

바이너리는 바이트의 나열입니다. 당연히 이 바이트들은 유효한 문자열이 아니더라도, 어떤 형태로든 조합될 수 있습니다.

iex> String.valid?(<<239, 191, 191>>)
false

문자열을 연결하는 연산자는 사실 바이너리를 연결하는 연산자입니다.

iex> <<0, 1>> <> <<2, 3>>
<<0, 1, 2, 3>>

Elixir에서는 문자열에 null 바이트 <<0>>를 붙여서 문자열의 실제 바이너리가 어떤지 확인하는 것은 무척 흔한 트릭입니다.

개인적인 감상으로는 그냥 유틸리티 함수를 하나 만들어주면 안되는 것인가, 싶음. 내부적으로 묵시 형변환해서 바이너리가 되는 걸 이용한 트릭이지만…이왕이면…보기 쉽게….

iex> "hełło" <> <<0>>
<<104, 101, 197, 130, 197, 130, 111, 0>>

각 숫자들은 하나의 바이트를 나타내며, 당연하지만 255까지 존재합니다. 바이너리는 255보다 큰 문자 코드를 저장하기 위한 방법을 제공합니다.

iex> <<255>>
<<255>>
iex> <<256>> # truncated
<<0>>
iex> <<256 :: size(16)>> # use 16 bits (2 bytes) to store the number
<<1, 0>>
iex> <<256 :: utf8>> # the number is a code point
"Ā"
iex> <<256 :: utf8, 0>>
<<196, 128, 0>>

만약 바이트가 8비트를 사용하고, 크기를 1비트로 제한하면 어떻게 될까요?

iex> <<1 :: size(1)>>
<<1::size(1)>>
iex> <<2 :: size(1)>> # truncated
<<0::size(1)>>
iex> is_binary(<< 1 :: size(1)>>)
false
iex> is_bitstring(<< 1 :: size(1)>>)
true
iex> bit_size(<< 1 :: size(1)>>)
1

이제 이 값은 더이상 바이너리가 아닌 비트문자열이 됩니다. 이게 무슨소리냐, 하면 바이너리는 8비트를 사용하는 비트문자열의 다른 이름이라는거죠.

바이너리/비트문자열에서도 패턴 매칭을 사용할 수 있습니다.

iex> <<0, 1, x>> = <<0, 1, 2>>
<<0, 1, 2>>
iex> x
2
iex> <<0, 1, x>> = <<0, 1, 2, 3>>
** (MatchError) no match of right hand side value: <<0, 1, 2, 3>>

바이너리에 포함되는 각 값들이 8비트로 매칭되어야 한다는 점을 기억하세요. 하지만, 바이너리 수식자를 사용하면 나머지 부분을 매칭할 수 있습니다.

iex> <<0, 1, x :: binary>> = <<0, 1, 2, 3>>
<<0, 1, 2, 3>>
iex> x
<<2, 3>>

이 패턴은 바이너리가 <<>>의 가장 마지막에서만 동작합니다. 문자열 연결 연산자 <>를 통해서도 비슷한 결과를 가져올 수 있습니다.

iex> "he" <> rest = "hello"
"hello"
iex> rest
"llo"

마무리하죠. 문자열은 UTF-8로 인코딩된 바이너리이며, 이 바이너리는 8로 나눌 수 있는 비트 문자열로 구성됩니다. 이는 Elixir가 유연하게 바이트와 바이너리를 처리한다는 점을 보여주지만, 대부분의 경우는 is_binary/1byte_size/1 함수로 충분할겁니다.

열심히 좋다고 찬양하고, 근데 쓸 일 없을거야, 라니… ㅇ<-<

문자 리스트

문자 리스트는 그냥 문자들의 목록입니다.

iex> 'hełło'
[104, 101, 322, 322, 111]
iex> is_list 'hełło'
true
iex> 'hello'
'hello'

문자 리스트는 바이트를 포함하는 대신에 문자 코드를 작은 따옴표로 표현합니다(IEx는 ASCII 범위를 벗어나는 문자가 존재하는 경우에만 문자 코드를 직접 출력합니다). 큰 따옴표를 사용하여 문자열(i.e. a binary)을 표현하는 대신, 작은 따옴표는 문자 리스트(i.e. a list)를 나타냅니다.

문자 리스트는 주로 Erlang에 대한 인터페이스에서 많이 사용되는데, 이는 오래된 몇몇 라이브러리에서 바이너리를 인자로 넘길 수 없기 때문입니다. to_string/1to_char_list/1를 사용해서 문자 리스트를 문자열로, 문자열을 문자 리스트로 변환할 수 있습니다.

iex> to_char_list "hełło"
[104, 101, 322, 322, 111]
iex> to_string 'hełło'
"hełło"
iex> to_string :hello
"hello"
iex> to_string 1
"1"

이 함수들은 여러가지 인자를 받을 수 있다는 점을 기억하세요. 문자 리스트를 문자열로 변환하는 것 뿐만 아니라, 정수, 아톰 등도 문자열로 변환할 수 있습니다.

Reference

Array