C'est un vaste sujet ! Il est possible d'écrire un long livre à ce sujet, et plusieurs personnes l'ont fait (cherche « unicode book » sur Amazon pour en voir quelques exemples).
La gestion des caractères dans les ordinateurs était bien plus simple il y a quelques décennies, quand les programmeurs partaient du principe que l'anglais était la seule langue importante. Donc : 26 lettres, majuscules et minuscules, 10 chiffres, quelques signes de ponctuation, plus un code (0x07) pour faire sonner une cloche, et tout cela tenait sur 7 bits : le jeu de caractères ASCII.
Naturellement, les gens ont commencé à demander ce qu'il en était de à, ä et Ł, puis d'autres ont demandé pour ऄ, ஹ et ญ, et les jeunes voulaient des emojis 😱. Que faire ?
Pour faire court, beaucoup de personnes intelligentes et patientes ont dû siéger pendant des années dans des comités, pour définir les détails du jeu de caractères Unicode et des encodages comme UTF-8, et beaucoup de logiciels ont eu besoin d'une réécriture très compliquée. Et beaucoup de nouveaux bugs ont été introduits.
Pour éviter que tout ne casse, la conception d'Unicode/UTF-8 garantit que les 127 premiers codes sont identiques à ceux de l'ASCII (même la cloche).
Les langages conçus après 2005 environ ont l'immense avantage qu'une norme Unicode raisonnablement stable existait déjà.
Julia (sorti pour la première fois en 2012) a donc pu partir du principe que tout serait en Unicode : les caractères, les strings, les noms de variables et de fonctions, les opérateurs mathématiques...
Pour citer le manuel : « Julia makes dealing with plain ASCII text simple and efficient, and handling Unicode is as simple and efficient as possible. » Remarque bien le « as possible » : c'est un élément important de cette affirmation.
Les littéraux de caractère s'écrivent entre guillemets simples et se distinguent des strings, qui s'écrivent entre guillemets doubles.
C'est évident pour les personnes qui viennent du monde du C/C++, mais cela peut dérouter les programmeurs Python et 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)
On voit dans ces exemples que le type est Char, et que Julia dispose d'informations supplémentaires sur la catégorie du caractère.
En y regardant de plus près, ces caractères peuvent être représentés par 4 chiffres hexadécimaux. Le jeu de caractères complet nécessite jusqu'à 6 chiffres hexadécimaux.
Ces nombres sont appelés « points de code » et vont actuellement de U+0000 à U+10FFFF.
Ils s'affichent dans le REPL, mais dans le code, utilise codepoints() pour les obtenir.
La conversion entre Char et Int est simple :
julia> Int('a')
97
julia> Char(97)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
Le compilateur autorise certaines formes d'arithmétique entière sur les 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)
Un sous-ensemble des fonctions de manipulation de strings peut aussi prendre des Char en entrée.
uppercase() et lowercase().isuppercase() et islowercase().isletter(), couvre de nombreux alphabetsisdigit(), teste strictement les chiffres de 0 à 9isnumeric(), plus large que isdigit, donc true pour ¾ et pour divers systèmes d'écriture non européensisxdigit(), les chiffres hexadécimauxisascii(), un caractère antérieur à Unicodeispunct(), la ponctuationisspace(), tout caractère d'espacementisprint(), les caractères imprimables (l'opposé est iscntrl())islowercase('A') # false
uppercase('γ') # 'Γ': Unicode U+0393 (category Lu: Letter, uppercase)
ispunct('@') # true
isdigit('A') # false
isxdigit('A') # true
Pour vérifier qu'un caractère est présent dans une string, on dispose de in.
Note que cela diffère de la recherche de sous-chaînes :
# char search
julia> 'a' ∈ "xabcahliazlnkiw"
true
# substring search
julia> "a" ∈ "xabcahliazlnkiw"
use occursin(needle, haystack) for string containment
Par ailleurs, les expressions régulières (qui font l'objet d'un autre concept) permettent des recherches et des manipulations puissantes.
Pour convertir une String en vecteur de Char, on peut utiliser collect().
Pour convertir un vecteur de Char en String, il y a le constructeur 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"
Cela fonctionne avec n'importe quels caractères, pas seulement l'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)
Note que le constructeur String() opère sur un Vector.
Pour convertir un seul Char en une string d'un seul caractère, la fonction est string(), avec un s minuscule.
julia> string('a')
"a"
Tout ce qui précède dans ce document semble relativement simple, alors n'y a-t-il vraiment pas de quoi s'inquiéter ?
Malheureusement, c'est trop optimiste !
Une complication vient de la nécessité d'utiliser « jusqu'à » 6 chiffres hexadécimaux par point de code. Cela signifie que différents caractères occupent différentes quantités de mémoire lorsqu'ils sont encodés en UTF-8.
Un octet ne peut stocker que des nombres (non signés) jusqu'à 255, soit deux chiffres hexadécimaux, donc UTF-8 utilise un nombre variable d'octets (1 à 4) pour stocker un Char.
On les appelle « unités de code », et la fonction ncodeunits() renvoie le nombre nécessaire pour un caractère donné.
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
De plus, tout ce qui peut s'afficher à l'écran n'a pas forcément son propre point de code unique. Certains caractères visuellement distincts sont considérés comme dérivés d'autres, si bien qu'Unicode les traite comme un caractère parent suivi d'un modificateur.