엘릭서에서 문자열에 대해 알아보고, 문자열이 어떻게 바이너리로 변하는지 알아보겠습니다.
데이터 타입에 대한 게시물에서 설명했듯이 엘릭서의 문자열은 실제로 UTF-8로 인코딩된 문자열의 바이트를 포함하는 바이너리입니다.
iex> is_binary("Speedy Taco")
true
그러나 여전히 문자열 타입으로 생각할 수 있습니다. 정말 자세히 살펴보지 않는 이상 다른 점을 알 수 없습니다. 문자열로 할 수 있는 모든 작업은 문자열이 바이너리라는 사실조차 모른 채 수행할 수 있습니다. 데이터 타입에 대한 이러한 사고방식은 엘릭서에서 흔히 볼 수 있습니다.
유니코드와 UTF 인코딩에 대해 자세히 설명하지는 않겠지만, 관심이 있는 분들을 위해 간략하게 설명하겠습니다. 유니코드는 라틴 문자부터 키릴문자, 한자, 좀 더 모호한 문자, 심지어 이모지와 같은 그래픽 문자에 이르기까지 수많은 문자로 구성되어 있습니다.
각 유니코드 문자는 코드 포인트라고 하는 숫자와 관련이 있습니다. 가능한 코드 포인트의 범위는 0에서 0x10FFFF까지 이지만, 현재 0x2ffff 이상의 모든 코드 포인트는 대부분 할당되지 않았습니다. 처음 128개의 코드 포인트는 유니코드 이전의 아스키 텍스트와 호환되도록 기본 아스키와 1 대 1로 대응됩니다.
인코딩은 이러한 코드 포인트를 저장하는 데 사용하는 방법을 말합니다. 유니코드 코드 포인트를 인코딩하는 가장 쉬운 방법은 각 문자에 32비트를 사용하는 것입니다. 32비트 문자의 간단한 배열은 현재 유니코드 표준의 모든 코드 포인트를 저장합니다. 코드 포인트를 저장하는 데 사용되는 바이너리 청크를 설명하는 각 코드 단위가 32비트이기 때문에 이 인코딩은 UTF-32로 알려져 있습니다.
UTF-32는 인코딩이 매우 비효율적이므로 거의 사용되지 않습니다. 일반적으로 사용하는 대부분의 코드 포인트는 저장하는 데 있어서 훨씬 적은 메모리를 필요로 합니다. 이 게시글처럼 표준 라틴 문자로만 구성된 간단한 문서의 경우, 단순 아스키를 사용하면 UTF-32보다 4배 더 작아집니다.
저장되는 대부분의 텍스트는 코드 포인트 범위의 아래쪽에 위치하므로 일반적으로 더 효율적인 인코딩이 사용됩니다.
UTF-16은 16비트 코드 단위(코드 단위는 인코딩의 기본 구성 요소)를 사용하여 코드 포인트를 표현합니다. 하나의 16비트 코드 단위는 일반적으로 사용되는 거의 모든 문자를 저장할 수 있으며, 드물게 매우 높은 숫자의 코드 포인트가 사용되는 경우에는 두 개의 16배트 코드 단위로 저장됩니다. 즉, 한 문자를 나타내는 코드 단위의 수는 한 개 또는 두 개가 될 수 있습니다.
UTF-8은 8비트 코드 단위를 사용하여 코드 포인트를 나타냅니다. 이는 일반적인 문자에 가장 효율적이고 아스키와 하위 호환성을 가지기 때문에 가장 일반적인 인코딩(엘릭서에서 사용하는 인코딩이기도 합니다)입니다. 단일 UTF-8 코드 단위는 기본 아스키(유니코드 이전의 대부분의 텍스트에 사용되었던)와 구분할 수 없으므로 아스키로 인코딩된 모든 텍스트도 유효한 UTF-8입니다. UTF-8은 모든 유니코드 코드 포인트를 표현하는데 1~4개의 8비트 코드 단위(각각 1바이트에 해당)를 사용하므로 문자당 바이트 수는 가변적입니다.
UTF-8은 각 문자가 몇 바이트로 구성되어 있는지 파악하기 위해 바이트를 검사해야 하므로 단순한 아스키 바이트 배열보다 더 정교한 코드가 필요하지만, 특히나 많은 양의 텍스트 인코딩에 1바이트만 필요한 것을 고려하면 메모리 효율이 매우 높습니다. 하지만 이 모든 것은 눈에 보이지 않습니다. 엘릭서가 알아서 처리해줍니다. 정말 원한다면 문자열의 바이너리 데이터를 검사하여 UTF-8 바이트를 직접 확인할 수 있습니다.
저는 이 주제가 특히나 흥미로워서 몇 년 전에 UTF-8 및 UTF-16으로 인코딩된 문자열로 작업할 수 있는 UtfString이라는 C++ 라이브러리(깃허브에서 사용 가능)를 작성했습니다. 이 라이브러리를 작성하면서 UTF-8 및 UTF-16 인코딩에 대해 많은 것을 배웠기 때문에 그 자체만으로도 충분히 가치가 있었습니다.
엘릭서의 문자열 처리는 루비의 문자열 처리와 매우 유사하다고 들었는데, 엘릭서 창시자인 조세 발림이 루비 생태계에서 매우 활발히 활동했다는 점을 고려하면 놀라운 일이 아닙니다.
엘릭서의 표준 문자열은 큰따옴표를 사용하여 문자열임을 나타냅니다.
iex> "This is a string"
"This is a string"
문자열은 <>
연산자를 사용하여 연결될 수 있습니다.
iex> "Chicken" <> "Foot"
"ChickenFoot"
문자열 리터럴은 "시길"이라는 것을 사용하여 지정할 수도 있습니다. 저는 아직 시길이 무엇인지 정확히 이해하지 못했지만, 엘릭서에서 일종의 데이터 리터럴을 지정하는 방법으로 보입니다. 시길은 항상 물결표 문자(~)로 시작하고 그 뒤에 문자가 오는 것으로 보입니다. 문자열 시길은 ~()이므로 이 시길 구문을 사용하여 "캡틴 에이헙"과 같은 문자열을 지정할 수 있습니다.
iex> "Captain Ahab"
"Captain Ahab"
iex> ~s(Captain Ahab)
"Captain Ahab"
""
표기법 대신 시길을 사용하는지 잘은 모르겠습니다. 아마도 문자열에 큰따옴표 문자를 이스케이프 처리하지 않고 좀 더 쉽게 넣기 위한 것 같습니다.
iex> ~s(This "string" contains "double quotes")
"This \"string\" contains \"double quotes\""
문자열 보간은 런타임에 평가될 문자열 리터럴에 표현식을 넣는 방법입니다. 결과는 문자열로 합쳐집니다. 이는 높은 가독성을 유지하면서 문자열을 구성하는 편리한 방법입니다.
자바스크립트(ES6 이상)에는 문자열 보간 기능이 있습니다.
"Next year, my dog will be ${dogAge + 1} years old"
C#에도 문자열 보간 기능이 있지만, 이는 최근에 추가된 기능(C# 6) 중 하나입니다.
$"Next year, my dog will be {dog.Age + 1} years old"
엘릭서에서 문자열 보간은 문자열 내에서 "#{}" 표기법을 사용하여 수행할 수 있습니다.
iex> name = "Bob"
"Bob"
iex> "Hello, my name is #{name}"
"Hello, my name is Bob"
괄호 사이에 어떤 표현식이라도 넣을 수 있으며 문자열로 변환됩니다.
iex> x = 4
4
iex> y = 5
5
iex> "The sum is #{x + y}"
"The sum is 9"
데이터 구조가 보간을 통해 문자열로 변환되는 방식을 확인하기 위해 이 기능을 조금 사용해 보았습니다. 리스트가 리스트 리터럴이 아니라 바이너리로 표시되는 것으로 보입니다. 다른 데이터 구조도 마찬가지일 것으로 예상합니다.
iex> list = [1, 2, 3]
[1, 2, 3]
iex> "The contents of the list are #{list}"
<<84, 104, 101, 32, 99, 111, 110, 116, 101, 110, 116, 115, 32, 111, 102,
32, 116, 104, 101, 32, 108, 105, 115, 116, 32, 97, 114, 101, 32, 1, 2,
3>>
엘릭서 문자열 모듈에는 문자열을 조작하는 함수가 포함되어 있습니다. 모듈과 엘릭서 표준 라이브러리에 대해서는 다음 글에서 다룰 예정이지만, 여기서 몇 가지 예제를 소개하겠습니다.
iex> String.length(string)
16
iex> String.codepoints(string)
["T", "h", "i", "s", " ", "i", "s", " ", "a", " ", "s", "t", "r", "i",
"n", "g"]
iex> String.at(string, 3)
"s"
iex> String.upcase(string)
"THIS IS A STRING"
히어독은 일반적인 함수 외부의 코드 문서화에 사용되는 여러 줄의 문자열이지만, 함수 내부에서 여러 줄 문자열 리터럴을 지정하는 데 사용할 수도 있습니다. 히어독 리터럴 문맥 내에서 개행 문자는 문자열 값의 일부가 됩니다. 히어독을 시작하고 끝내는 삼중 따옴표는 각자의 줄에 있어야 합니다.
iex> """
...> This is a multiline string
...> that goes on for multiple lines
...> """
"This is a multiline string\nthat goes on for multiple lines\n"