이것은 큰 주제예요! 이 주제에 관해서는 긴 책 한 권을 쓸 수도 있고, 실제로 그렇게 한 사람들도 여럿 있어요 (아마존에서 "unicode book"을 검색하면 몇 가지 예를 볼 수 있어요).
예전에는 컴퓨터에서 문자를 다루는 일이 훨씬 단순했어요. 그때 프로그래머들은 영어만이 중요한 언어라고 가정했거든요. 그래서 알파벳 26자(대문자와 소문자), 숫자 10개, 몇 가지 구두점, 그리고 종을 울리는 코드(0x07)까지 합쳐서 7비트 안에 모두 들어갔어요. 바로 ASCII 문자 집합이에요.
그러자 자연스럽게 사람들은 à, ä, Ł는 어떻게 하냐고 묻기 시작했고, 또 다른 사람들은 ऄ, ஹ, ญ는 어떻게 하냐고 물었어요. 그리고 젊은 사람들은 이모지 😱를 원했죠. 어떻게 해야 했을까요?
간단히 말하자면, 똑똑하고 인내심 강한 많은 사람이 수년간 위원회에서 일하며 유니코드 문자 집합과 UTF-8 같은 인코딩의 세부 사항을 정리해야 했어요. 그리고 수많은 소프트웨어를 아주 복잡하게 다시 작성해야 했죠. 게다가 새로운 버그도 많이 생겼어요.
모든 것이 깨지는 걸 막기 위해, 유니코드/UTF-8 설계에서는 처음 127개 코드가 ASCII와 동일하도록 했어요 (종까지도요).
2005년쯤 이후에 설계된 언어들은 이미 어느 정도 안정적인 유니코드 표준이 존재했다는 큰 이점을 누렸어요.
Julia(2012년에 처음 공개됐어요)는 문자, 문자열, 변수와 함수 이름, 수학 연산자까지 모든 것이 유니코드일 거라고 가정할 수 있었어요.
설명서를 인용하자면, "Julia는 일반 ASCII 텍스트를 다루는 일을 간단하고 효율적으로 만들어 주고, 유니코드를 다루는 일도 가능한 한 간단하고 효율적으로 해줘요." "가능한 한"이라는 부분에 주목해요. 이는 이 문장에서 중요한 부분이에요.
문자 리터럴은 작은따옴표로 쓰고, 큰따옴표로 쓰는 문자열과는 구별돼요.
C/C++ 세계에서 온 사람들에게는 당연한 일이지만, Python과 JavaScript 프로그래머에게는 헷갈릴 수 있어요.
julia> a = 'a' # Roman alphabet
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
julia> typeof(a)
Char
julia> jha = 'झ' # Devanagari alphabet
'झ': Unicode U+091D (category Lo: Letter, other)
julia> typeof(jha)
Char
julia> '❤' # heart emoji
'❤': Unicode U+2764 (category So: Symbol, other)
예제를 보면 타입이 Char라는 것을 알 수 있고, Julia는 문자의 분류에 대한 정보도 더 제공해요.
자세히 보면 이 문자들이 4자리 16진수로 표현될 수 있다는 것을 알 수 있어요. 전체 문자 집합에는 최대 6자리 16진수가 필요해요.
이 숫자를 "코드 포인트"라고 부르고, 현재 U+0000부터 U+10FFFF까지의 범위를 가져요.
REPL에는 표시되지만, 코드 안에서는 codepoints()를 사용해서 얻을 수 있어요.
Char와 Int 사이를 변환하는 것은 간단해요:
julia> Int('a')
97
julia> Char(97)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
컴파일러는 Char에 대해 일부 형태의 정수 연산을 허용해요:
julia> 'b' - 'a' # interval, in alphabetic order
1
julia> 'b' + 'a'
ERROR: MethodError: no method matching +(::Char, ::Char)
julia> 'a' + 5
'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)
julia> 'f' + ('A' - 'a') # same as `uppercase('f')`
'F': ASCII/Unicode U+0046 (category Lu: Letter, uppercase)
문자열을 다루는 함수 중 일부는 Char 입력에도 동작해요.
uppercase()와 lowercase()로 대소문자를 바꿔요.isuppercase()와 islowercase()로 대소문자를 검사해요.isletter(), 여러 알파벳을 포괄해요isdigit(), 엄격하게 0:9만 검사해요isnumeric(), isdigit보다 넓어서 ¾와 여러 비유럽 문자 체계에도 true를 반환해요isxdigit(), 16진수 숫자예요isascii(), 유니코드 이전 문자예요ispunct(), 구두점이에요isspace(), 모든 공백 문자예요isprint(), 인쇄 가능한 문자예요 (반대는 iscntrl())islowercase('A') # false
uppercase('γ') # 'Γ': Unicode U+0393 (category Lu: Letter, uppercase)
ispunct('@') # true
isdigit('A') # false
isxdigit('A') # true
문자열 안에 특정 문자가 있는지 확인하려면 in이 있어요.
이것은 부분 문자열과 다르다는 점에 주목해요:
# char search
julia> 'a' ∈ "xabcahliazlnkiw"
true
# substring search
julia> "a" ∈ "xabcahliazlnkiw"
use occursin(needle, haystack) for string containment
또한 정규 표현식(다른 개념에서 다루는 주제예요)은 강력한 검색과 조작을 가능하게 해줘요.
문자열을 Char 벡터로 바꿀 때는 collect()를 사용할 수 있어요.
Char 벡터를 문자열로 바꿀 때는 String() 생성자가 있어요.
julia> s = "abcde"
"abcde"
julia> cv = collect(s)
5-element Vector{Char}:
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)
julia> String(cv)
"abcde"
이것은 ASCII뿐만 아니라 어떤 문자에서도 동작해요.
julia> collect("❤,😱")
3-element Vector{Char}:
'❤': Unicode U+2764 (category So: Symbol, other)
',': ASCII/Unicode U+002C (category Po: Punctuation, other)
'😱': Unicode U+1F631 (category So: Symbol, other)
String() 생성자는 벡터에 동작한다는 점에 주목해요.
Char 하나를 1글자 문자열로 _캐스트_하려면 소문자 s를 쓴 string() 함수를 사용해요.
julia> string('a')
"a"
지금까지 이 문서에서 다룬 내용은 비교적 단순해 보여요. 그럼 정말 걱정할 게 별로 없을까요?
안타깝게도, 그건 너무 낙관적인 생각이에요!
한 가지 복잡한 점은 코드 포인트당 "최대" 6자리 16진수가 필요하다는 거예요. 즉, UTF-8로 인코딩하면 문자마다 메모리에서 필요한 공간의 크기가 달라요.
바이트 하나는 (부호 없는) 숫자를 255까지, 즉 16진수 두 자리까지만 저장할 수 있어요. 그래서 UTF-8은 Char 하나를 저장하는 데 1바이트에서 4바이트까지 가변적인 바이트 수를 사용해요.
이것을 "코드 단위"라고 부르고, ncodeunits() 함수가 주어진 문자에 필요한 개수를 반환해요.
julia> codepoint(jha) # jha 'झ' is defined in an earlier example
0x0000091d
julia> ncodeunits(jha)
3
julia> ncodeunits('a') # ASCII character
1
julia> ncodeunits('😱') # emoji
4
또한 화면에 표시될 수 있는 모든 것이 각자 고유한 코드 포인트를 가지는 것은 아니에요. 시각적으로 구별되는 일부 문자는 다른 문자에서 파생된 것으로 간주돼요. 그래서 유니코드는 이를 기본 문자와 수정자의 조합으로 취급해요.