fc is how to compare without case¶
Level: 201 · you compare user input, keys or names regardless of case
One line: lc($a) eq lc($b) says Straße and STRASSE differ, and so do σας and ΣΑΣ; fc — fold case, use v5.16 and later — says they match, as /i already did; and along the way uc 'ß' is two letters, lc 'İ' is two code points, and no language's own rule, like Turkish I to ı, ever applies.
Measured¶
Verified output of fc_pl.pl — regenerated by tools/run_examples.py, never hand-typed.
--- lc, uc and fc ---
Straße: lc straße uc STRASSE fc strasse
STRASSE: lc strasse uc STRASSE fc strasse
σας: lc σας uc ΣΑΣ fc σασ
ΣΑΣ: lc σασ uc ΣΑΣ fc σασ
İstanbul: lc i̇stanbul uc İSTANBUL fc i̇stanbul
--- comparing ---
lc('Straße') eq lc('STRASSE') no
fc('Straße') eq fc('STRASSE') yes
lc('σας') eq lc('ΣΑΣ') no
fc('σας') eq fc('ΣΑΣ') yes
'STRASSE' =~ /^straße$/i yes
--- lengths change ---
length 'ß' = 1, length uc 'ß' = 2
length 'İ' = 1, length lc 'İ' = 2 (U+0069 U+0307)
--- title case is a third case ---
ucfirst 'džungla' = Džungla (U+01C5), uc 'džungla' = DŽUNGLA (U+01C4)
--- one mapping for every language ---
lc 'I' = i (U+0069) Turkish lower-cases I to ı, U+0131
Why lc is not enough¶
lc maps a string to lower case, and the lower case of STRASSE is strasse while the lower case of Straße is straße. Case folding is a different mapping, made for comparison: it sends both to one form, and ß folds to ss.
Greek has two lower-case sigmas, σ inside a word and ς at the end. fc sends both to σ. lc has no notion of a word's end, so lc('ΣΑΣ') is σασ — not the correctly spelled σας, and not equal to it.
/i in a regex already uses folding, so 'STRASSE' =~ /^straße$/i matches even though the lengths differ.
Lengths change¶
uc 'ß' is SS, one letter becoming two. lc 'İ' — the Turkish capital dotted I — is i followed by U+0307 COMBINING DOT ABOVE, one code point becoming two. Anything that assumed a case change keeps the length — a fixed-width field, a cursor position, an index computed before the change — is wrong afterwards.
Title case is a third case¶
dž is one code point standing for the Croatian digraph dž, and it has three cases: lower dž, upper DŽ and title Dž, for the start of a capitalised word. ucfirst uses the title case, uc the upper.
One mapping for every language¶
lc 'I' is i. In Turkish and Azerbaijani the lower case of I is ı, dotless, and İ pairs with i — but Perl's lc, uc and fc take no language, and apply Unicode's default mappings everywhere. A program that needs Turkish rules needs a library that takes a locale, such as ICU; the C library's ICU4C: Unicode text in C ↗ shows one.
What to do instead¶
fc($a) eq fc($b) # compare
$seen{ fc $name }++ # key a hash
sort { fc($a) cmp fc($b) or $a cmp $b } @names # sort, with a tie-break
Fold after normalizing, not instead of it — see length counts code points — and remember fc needs use v5.16 or later, which use v5.36 includes.
If you are coming from another language¶
- Python.
str.casefold()isfc. See Lowercasing is not folding ↗. - Java.
toLowerCase()with no argument uses the default locale — the opposite problem, a language rule you did not ask for. See Case is locale-sensitive ↗. - Rust.
str::to_uppercase↗ turnsßintoSStoo, and Wrong, but not unsafe ↗ is the story of Rust's final sigma. - The encodings library. Case is not a per-character operation ↗.
See also¶
sortis not alphabetical —fcinside a sort block, and what it still does not fix- The Unicode bug — when
uc 'é'does nothing at all - fc ↗