¡Este es un tema enorme! Es posible escribir un libro largo al respecto, y varias personas lo han hecho (busca «unicode book» en Amazon para ver algunos ejemplos).
Manejar caracteres en las computadoras era mucho más simple en décadas anteriores, cuando quienes programaban asumían que el inglés era el único idioma importante. Entonces: 26 letras, mayúsculas y minúsculas, 10 dígitos, varios signos de puntuación, más un código (0x07) para hacer sonar una campana, y todo cabía en 7 bits: el conjunto de caracteres ASCII.
Naturalmente, la gente empezó a preguntar qué pasaba con à, ä y Ł, luego otras personas empezaron a preguntar por ऄ, ஹ y ญ, y las personas jóvenes querían emojis 😱. ¿Qué hacer?
Para resumir una larga historia, muchas personas inteligentes y pacientes tuvieron que participar en comités durante años, definiendo los detalles del conjunto de caracteres Unicode y de codificaciones como UTF-8, y mucho software necesitó una reescritura muy complicada. Además, se introdujeron muchos bugs nuevos.
Para evitar que todo se rompiera, el diseño de Unicode/UTF-8 garantiza que los primeros 127 códigos son idénticos a ASCII (incluso la campana).
Los lenguajes diseñados después de 2005 aproximadamente tienen la enorme ventaja de que ya existía un estándar Unicode razonablemente estable.
Julia (publicado por primera vez en 2012) pudo asumir que todo sería Unicode: caracteres, strings, nombres de variables y de funciones, operadores matemáticos...
Para citar el manual: «Julia hace que tratar con texto ASCII simple sea simple y eficiente, y manejar Unicode es tan simple y eficiente como sea posible». Fíjate en el «como sea posible»: es una parte importante de la afirmación.
Los literales de carácter se escriben entre comillas simples y son distintos de los strings escritos entre comillas dobles.
Esto es obvio para quienes vienen del mundo de C/C++, pero puede confundir a quienes programan en Python y 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)
En los ejemplos podemos ver que el tipo es Char, y que Julia tiene más información sobre la categoría del carácter.
Si miramos de cerca, parece que estos caracteres se pueden representar con 4 dígitos hexadecimales. El conjunto completo de caracteres necesita hasta 6 dígitos hexadecimales.
Estos números se llaman «puntos de código» y actualmente van desde U+0000 hasta U+10FFFF.
Se muestran en el REPL, pero dentro del código usa codepoints() para obtenerlos.
Convertir entre Char e Int es simple:
julia> Int('a')
97
julia> Char(97)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
El compilador permite algunas formas de aritmética de enteros con 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)
Muchas funciones para manejar strings también funcionan cuando reciben un Char como argumento.
uppercase() y lowercase().isuppercase() e islowercase().isletter(), abarca muchos alfabetosisdigit(), comprueba estrictamente 0:9isnumeric(), más amplia que isdigit, así que da true para ¾ y varios sistemas de escritura no europeosisxdigit(), dígitos hexadecimalesisascii(), carácter anterior a Unicodeispunct(), puntuaciónisspace(), cualquier carácter de espacio en blancoisprint(), caracteres imprimibles (lo contrario es iscntrl())islowercase('A') # false
uppercase('γ') # 'Γ': Unicode U+0393 (category Lu: Letter, uppercase)
ispunct('@') # true
isdigit('A') # false
isxdigit('A') # true
Para pasar de un String a un vector de Char, podemos usar collect().
Para pasar de un vector de Char a un String, está el constructor 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"
Esto funciona con cualquier carácter, no solo con 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)
Fíjate en que el constructor String() trabaja sobre un Vector.
Para convertir un solo Char a un string de 1 carácter, la función es string(), con la s minúscula.
julia> string('a')
"a"
Todo lo que hemos visto hasta ahora en este documento parece relativamente simple, así que ¿de verdad no hay mucho de qué preocuparse?
Lamentablemente, ¡esto es demasiado optimista!
Una complicación viene de la necesidad de hasta 6 dígitos hexadecimales por punto de código. Esto significa que distintos caracteres necesitan distintas cantidades de espacio en memoria cuando se codifican en UTF-8.
Un byte solo puede almacenar números (sin signo) hasta 255, dos dígitos hexadecimales, así que UTF-8 usa una cantidad variable de bytes (de 1 a 4) para almacenar un Char.
Estos se llaman «unidades de código», y la función ncodeunits() devuelve la cantidad necesaria para un carácter dado.
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
Además, no todo lo que se puede mostrar en pantalla tiene su propio punto de código único. Algunos caracteres visualmente distintos se consideran derivados de otros, así que Unicode los trata como un carácter base más un modificador.
Este problema afecta a los strings, donde presenta dificultades para la indexación.
En este ejercicio implementarás un conjunto parcial de rutinas de utilidad para ayudar a un desarrollador a limpiar nombres de identificadores.
A lo largo de las 6 tareas irás construyendo poco a poco las funciones transform, para convertir caracteres individuales, y clean, para convertir strings.
Un identificador válido se compone de cero o más letras, guiones bajos, guiones, signos de interrogación y emojis.
Si se pasa un string vacío a la función clean, se debe devolver un string vacío.
Implementa la función transform para reemplazar los guiones por guiones bajos.
julia> transform('-')
"_"
Elimina todos los caracteres de espacio en blanco. Esto incluye los espacios en blanco al principio y al final.
julia> transform(' ')
""
Modifica la función transform para convertir camelCase a kebab-case
julia> transform('D')
"-d"
Modifica la función transform para omitir los caracteres que sean numéricos.
julia> transform('7')
""
Modifica la función transform para reemplazar las letras griegas en el rango de 'α' a 'ω'.
julia> transform('β')
"?"
Implementa la función clean para aplicar estas operaciones a un string completo.
Los caracteres que queden fuera de las reglas deben pasar sin cambios.
julia> clean(" a2b Cd-ω😀 ")
"ab-cd_?😀"
Regístrate en Exercism para aprender y dominar Julia con 35 conceptos128 ejercicios y mentoría humana real, todo gratis.