이건 큰 주제예요! 이 주제로 두꺼운 책 한 권을 쓸 수도 있고, 실제로 그렇게 한 사람들도 여럿 있어요 (아마존에서 "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는 문자 분류에 대한 정보도 더 가지고 있어요.
자세히 보면 이 문자들은 16진수 4자리로 표현할 수 있어요. 전체 문자 집합에는 16진수 6자리까지 필요하죠.
이런 숫자를 "코드 포인트"라고 부르고, 현재 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
문자열을 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 하나를 한 글자짜리 문자열로 _형 변환_하려면 소문자 s를 쓴 string() 함수를 사용해요.
julia> string('a')
"a"
지금까지 나온 내용은 모두 비교적 단순해 보여요. 그럼 정말 걱정할 게 별로 없는 걸까요?
안타깝게도, 그건 너무 낙관적인 생각이에요!
코드 포인트마다 16진수 "최대" 6자리까지 필요하다는 점에서 문제가 하나 생겨요. 그래서 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
또한 화면에 표시할 수 있는 모든 것에 고유한 코드 포인트가 있는 건 아니에요. 시각적으로 구별되는 일부 문자는 다른 문자에서 파생된 것으로 간주되어, 유니코드에서는 기본 문자와 수정자의 결합으로 취급해요.
이 문제는 문자열에도 영향을 주는데, 인덱싱을 까다롭게 만들어요.
이 연습 문제에서는 개발자가 식별자 이름을 정리할 수 있도록 돕는 유틸리티 함수 일부를 구현해요.
6개의 과제를 통해 단일 문자를 변환하는 transform 함수와 문자열을 변환하는 clean 함수를 차근차근 완성해 나가요.
유효한 식별자는 0개 이상의 글자, 밑줄, 하이픈, 물음표, 이모지로 이루어져요.
clean 함수에 빈 문자열이 전달되면 빈 문자열이 반환되어야 해요.
transform 함수를 구현해 하이픈을 밑줄로 바꿔요.
julia> transform('-')
"_"
모든 공백 문자를 제거해요. 문자열 앞뒤의 공백도 포함돼요.
julia> transform(' ')
""
transform 함수를 수정해 camelCase를 kebab-case로 변환해요
julia> transform('D')
"-d"
transform 함수를 수정해 숫자인 문자를 제외해요.
julia> transform('7')
""
transform 함수를 수정해 'α'부터 'ω' 범위의 그리스 문자를 바꿔요.
julia> transform('β')
"?"
clean 함수를 구현해 이 연산들을 문자열 전체에 적용해요.
규칙에 해당하지 않는 문자는 그대로 유지돼요.
julia> clean(" a2b Cd-ω😀 ")
"ab-cd_?😀"
Exercism에 가입하고 Julia 트랙을 개념 35개연습 문제 128개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.