小编典典

为什么现代 Perl 默认避免使用 UTF-8?

all

我想知道为什么大多数使用 Perl 构建的现代解决方案默认不启用UTF-8

我知道核心 Perl 脚本存在许多遗留问题,可能会破坏一些东西。但是,从我的角度来看,在 21世纪,大型新项目(或具有大视野的项目)应该从头开始使他们的软件
UTF-8
证明。我仍然没有看到它发生。例如,Moose启用严格和警告,但不启用UnicodeModern::Perl也减少了样板文件,但没有 UTF-8 处理。

为什么?在 2011 年的现代 Perl 项目中是否有一些理由避免使用 UTF-8?


评论@tchrist 太长了,所以我在这里添加它。

好像我没有说清楚。让我尝试添加一些东西。

tchrist 和我看到的情况非常相似,但我们的结论完全相反。我同意,Unicode 的情况很复杂,但这就是为什么我们(Perl
用户和编码人员)需要一些层(或 pragma)来使 UTF-8 处理变得像现在一样容易。

tchrist 指出了许多要涵盖的方面,我会阅读并思考几天甚至几周。不过,这不是我的观点。 tchrist 试图证明没有一种方法可以“启用
UTF-8”。我没有太多的知识可以与之争论。所以,我坚持活生生的例子。

我玩弄了Rakudo,而 UTF-8 就在 我需要的地方
。我没有任何问题,它只是工作。也许在更深的地方存在一些限制,但一开始,我测试的所有东西都按预期工作。

这不应该是现代 Perl 5 的目标吗?我更强调一点:我并不是建议将 UTF-8 作为核心 Perl 的默认字符集,我建议那些开发 新* 项目的人可以
快速触发它。
*

另一个例子,但语气更消极。框架应该使开发更容易。几年前,我尝试过 Web 框架,但因为“启用 UTF-8”太晦涩难懂而将它们扔掉了。
2 相同的问题: _如何使 Mason2 UTF-8
干净? 因此,它是一个相当新的框架,但将其与 UTF-8
一起使用需要深入了解其内部结构。它就像一个大红色标志:停止,不要使用我!

我真的很喜欢 Perl。但是处理Unicode是痛苦的。我仍然发现自己在靠墙奔跑。 tchrist 以某种方式是对的,它回答了我的问题:新项目不吸引
UTF-8,因为它在 Perl 5 中太复杂了。


阅读 120

收藏
2022-03-07

共1个答案

小编典典

饾尚饾凳饾櫌饾櫏饾櫋饾櫄饾櫒饾橹 : 饾煏 饾樋饾凳 饾櫒饾棹饾櫑饾櫄饾橹饾櫄饾檷饾櫫楾澹饾澄櫄饾櫍饾柜饾櫀饾橹饾凳饾櫎饾櫍饾櫒

  1. 将您的PERL_UNICODE变量设置为AS. 这使得所有 Perl 脚本都解码@ARGV为 UTF-8 字符串,并将 stdin、stdout 和 stderr 的所有三个编码设置为 UTF-8。这两个都是全局效应,而不是词汇效应。

  2. 在您的源文件(程序、模块、库、dohickey)的顶部,通过以下方式突出声明您正在运行 perl 版本 5.12 或更高版本:

    use v5.12;  # minimal for unicode string feature
    

    use v5.14; # optimal for unicode string feature

  3. 启用警告,因为之前的声明只启用了限制和特性,而不是警告。我还建议将 Unicode 警告提升为异常,因此请同时使用这两行,而不仅仅是其中之一。但是请注意,在 v5.14 下,utf8警告类包括其他三个可以单独启用的子警告:noncharsurrogatenon_unicode。这些您可能希望对其施加更大的控制。

    use warnings;
    

    use warnings qw( FATAL utf8 );

  4. 声明这个源单元被编码为 UTF-8。尽管曾几何时这个 pragma 做了其他事情,但现在它只为这个单一的目的服务,而没有其他目的:

    use utf8;
    
  5. 声明任何 在这个词法范围内但不在其他地方 打开文件句柄的东西都是假设该流是用 UTF-8 编码的,除非你另有说明。这样您就不会影响其他模块或其他程序的代码。

    use open qw( :encoding(UTF-8) :std );
    
  6. 通过 启用命名字符\N{CHARNAME}

    use charnames qw( :full :short );
    
  7. 如果您有DATA句柄,则必须显式设置其编码。如果你希望这是 UTF-8,那么说:

    binmode(DATA, ":encoding(UTF-8)");
    

当然,您最终可能会发现自己关心的其他问题没有尽头,但这些足以接近国家的目标,即“让一切都与 UTF 一起工作”8”,尽管对这些问题的感觉有所减弱条款。

另一个编译指示虽然与 Unicode 无关,但它是:

      use autodie;

这些天我自己的样板往往看起来像这样:

use 5.014;

use utf8;
use strict;
use autodie;
use warnings; 
use warnings    qw< FATAL  utf8     >;
use open        qw< :std  :utf8     >;
use charnames   qw< :full >;
use feature     qw< unicode_strings >;

use File::Basename      qw< basename >;
use Carp                qw< carp croak confess cluck >;
use Encode              qw< encode decode >;
use Unicode::Normalize  qw< NFD NFC >;

END { close STDOUT }

if (grep /\P{ASCII}/ => @ARGV) { 
   @ARGV = map { decode("UTF-8", $_) } @ARGV;
}

$0 = basename($0);  # shorter messages
$| = 1;

binmode(DATA, ":utf8");

# give a full stack dump on any untrapped exceptions
local $SIG{__DIE__} = sub {
    confess "Uncaught exception: @_" unless $^S;
};

# now promote run-time warnings into stack-dumped
#   exceptions *unless* we're in an try block, in
#   which case just cluck the stack dump instead
local $SIG{__WARN__} = sub {
    if ($^S) { cluck   "Trapped warning: @_" } 
    else     { confess "Deadly warning: @_"  }
};

while (<>)  {
    chomp;
    $_ = NFD($_);
    ...
} continue {
    say NFC($_);
}

__END__

说“Perl 应该 [以某种方式!] 默认情况下启用 Unicode”甚至没有开始考虑在某些罕见和孤立的情况下说足够多的东西,甚至有点用处。Unicode 不仅仅是一个更大的字符库。这也是这些角色如何以多种方式相互作用的方式。

即使是(某些)人似乎认为他们想要的头脑简单的最小措施也保证会严重破坏数百万行代码,这些代码没有机会“升级”到你漂亮的新美丽新世界现代性。

这比人们想象的要复杂得多。在过去的几年里,我一直在思考这个巨大的问题。我很想被证明我错了。但我不认为我是。从根本上说,Unicode 比您想强加给它的模型更复杂,而且这里的复杂性是您永远无法掩盖的。如果您尝试,您将破坏您自己的代码或其他人的代码。在某些时候,您只需要分解并了解 Unicode 是什么。你不能假装它不是。

不遗余力地让 Unicode 变得简单,远远超过我用过的任何其他东西。如果您认为这很糟糕,请尝试其他方法一段时间。然后回到:要么你会回到一个更美好的世界,要么你会带来同样的知识,这样我们就可以利用你的新知识让在这些事情上做得更好。

正如你所说,至少,这里有一些东西似乎是 “默认启用 Unicode”所必需的:

  1. 所有源代码默认为 UTF-8。你可以用use utf8or得到它export PERL5OPTS=-Mutf8
  2. DATA句柄应该是 UTF-8。您必须在每个包的基础上执行此操作,如binmode(DATA, ":encoding(UTF-8)").
  3. 脚本的程序参数默认应该理解为 UTF-8。 export PERL_UNICODE=A, 或perl -CA, 或export PERL5OPTS=-CA.
  4. 标准输入、输出和错误流应默认为 UTF-8。export PERL_UNICODE=S对于所有这些,或I,O和/或E只是其中一些。这就像perl -CS.
  5. 除非另有声明,否则由打开的任何其他句柄都应视为 UTF-8;export PERL_UNICODE=D或与其中i特定o的一起;export PERL5OPTS=-CD会工作。这-CSAD对他们所有人来说都是如此。
  6. 覆盖两个基地以及您打开的所有流export PERL5OPTS=-Mopen=:utf8,:std。请参阅单引号
  7. 您不想错过 UTF-8 编码错误。试试export PERL5OPTS=-Mwarnings=FATAL,utf8。并确保您的输入流始终binmode为 d :encoding(UTF-8),而不仅仅是 to :utf8
  8. 🐪 应该将 128–255 之间的代码点理解为对应的 Unicode 代码点,而不仅仅是无属性的二进制值。 use feature "unicode_strings"export PERL5OPTS=-Mfeature=unicode_strings。这将使uc("\xDF") eq "SS""\xE9" =~ /\w/。一个简单的export PERL5OPTS=-Mv5.12或更好的也能做到这一点。
  9. 默认情况下未启用命名的 Unicode 字符,因此添加export PERL5OPTS=-Mcharnames=:full,:short,latin,greek或类似的。请参阅uninamestcgrep
  10. 您几乎总是需要从标准Unicode::Normalize模块中访问各种类型的分解功能。 export PERL5OPTS=-MUnicode::Normalize=NFD,NFKD,NFC,NFKD,然后总是通过 NFD 运行传入的内容和来自 NFC 的传出内容。我知道这些还没有 I/O 层,但请参阅nfcnfdnfkdnfkc
  11. 🐪 中使用eq, ne, lc, cmp, sort, &c&cc 的字符串比较总是错误的。因此@a = sort @b,您需要@a = Unicode::Collate->new->sort(@b). 不妨将其添加到您的export PERL5OPTS=-MUnicode::Collate. 您可以缓存密钥以进行二进制比较。
  12. 🐪 内置插件喜欢printfwrite使用 Unicode 数据做错事。您需要为前者使用Unicode::GCString模块,而后者也需要使用Unicode::LineBreak模块。请参阅uwcunifmt
  13. 如果您希望它们计为整数,那么您将不得不通过函数\d+运行捕获,因为🐪 的内置atoi (3) 目前还不够聪明。Unicode::UCD::num
  14. 您将在 👽 文件系统上遇到文件系统问题。一些文件系统默默地强制转换为 NFC;其他人默默地强制转换为 NFD。而其他人仍在做其他事情。有些人甚至完全忽略了这件事,这导致了更大的问题。因此,您必须自己处理 NFC/NFD 以保持理智。
  15. a-z您所有涉及orA-Z等的 🐪 代码都必须更改,包括m//,s///tr///. 它应该作为一个尖叫的危险信号脱颖而出,表明您的代码已损坏。但目前尚不清楚它必须如何改变。获得正确的属性并了解它们的外壳,比您想象的要难。我每天都使用unicharsuniprops 。
  16. 使用\p{Lu}的代码几乎和使用[A-Za-z]. 您需要\p{Upper}改用,并知道原因。是的\p{Lowercase}\p{Lower}不同于\p{Ll}\p{Lowercase_Letter}
  17. 使用的代码[a-zA-Z]更糟糕。它不能使用\pLor \p{Letter}; 它需要使用\p{Alphabetic}. 不是所有的字母都是字母,你知道的!
  18. 如果您正在寻找 🐪 变量/[\$\@\%]\w+/,那么您就有问题了。您需要查找/[\$\@\%]\p{IDS}\p{IDC}*/,甚至不考虑标点符号变量或包变量。
  19. 如果您正在检查空格,那么您应该在 和 之间进行选择\h\v具体取决于。而且你不应该使用\s,因为它并不意味着 [\h\v],与流行的看法相反。
  20. 如果您使用\n的是线边界,甚至是\r\n,那么您做错了。你必须使用\R,这是不一样的!
  21. 如果您不知道何时以及是否调用Unicode::Stringprep,那么您最好学习一下。
  22. 不区分大小写的比较需要检查两个事物是否是相同的字母,无论它们的变音符号等。最简单的方法是使用标准的 Unicode::Collate模块。Unicode::Collate->new(level => 1)->cmp($a, $b). 还有eq方法之类的,你应该也了解一下matchsubstr方法。与 🐪 内置插件相比,它们具有明显的优势。
  23. 有时这还不够,您需要使用 Unicode::Collate::Locale模块,如 in Unicode::Collate::Locale->new(locale => "de__phonebook", level => 1)->cmp($a, $b)而不是。认为这Unicode::Collate::->new(level => 1)->eq("d", "ð")是真的,但Unicode::Collate::Locale->new(locale=>"is",level => 1)->eq("d", " ð")也是假的。同样,“ae”和“æ”是eq如果您不使用语言环境,或者如果您使用英语语言环境,但它们在冰岛语言环境中是不同的。怎么办?这很难,我告诉你。您可以使用 ucsort来测试其中的一些内容。
  24. 考虑如何匹配字符串“ niño ”中的模式CVCV(辅音、元音、辅音、元音)。它的 NFD 形式——你最好记得把它放进去——变成“nin\x{303}o”。现在你要做什么?即使假装元音是[aeiou](顺便说一句,这是错误的),你也不能做类似的事情(?=[aeiou])\X),因为即使在 NFD 中,像 ‘ø’ 这样的代码点也不会分解!但是,使用我刚刚向您展示的 UCA 比较,它将测试等于“o”。你不能依赖 NFD,你必须依赖 UCA。

这还不是全部。人们对 Unicode 有一百万个被打破的假设。在他们理解这些事情之前,他们的🐪代码将被破坏。

  1. 假定它可以在不指定编码的情况下打开文本文件的代码被破坏。
  2. 假定默认编码是某种本机平台编码的代码被破坏了。
  3. 假设日文或中文网页在 UTF-16 中占用的空间比在 UTF-8 中少的代码是错误的。
  4. 假设 Perl 在内部使用 UTF-8 的代码是错误的。
  5. 假设编码错误总是会引发异常的代码是错误的。
  6. 假设 Perl 代码点限制为 0x10_FFFF 的代码是错误的。
  7. 假设您可以设置$/为可以使用任何有效行分隔符的代码是错误的。
  8. 在 casefolding 上假设往返相等的代码,如lc(uc($s)) eq $sor uc(lc($s)) eq $s,完全被破坏和错误。考虑uc("σ")and uc("ς") are both "Σ",但lc("Σ")不可能同时返回这两个。
  9. 假设每个小写代码点都有一个不同的大写字母,反之亦然的代码被破坏了。例如,"ª"是没有大写的小写字母;而"ᵃ"and"ᴬ"都是字母,但它们不是小写字母;但是,它们都是小写代码点,没有对应的大写版本。知道了?他们不是 \p{Lowercase_Letter},尽管两者\p{Letter}兼而有之\p{Lowercase}
  10. 假设更改大小写不会更改字符串长度的代码已损坏。
  11. 假设只有两种情况的代码被破坏了。还有标题栏。
  12. 假设只有字母有大小写的代码被破坏了。事实证明,除了字母之外,数字、符号甚至标记都有大小写。事实上,改变大小写甚至可以使某些东西改变它的主要通用类别,比如\p{Mark}变成\p{Letter}. 它还可以使它从一个脚本切换到另一个脚本。
  13. 假定大小写从不依赖于语言环境的代码被破坏。
  14. 假设 Unicode 给出关于 POSIX 语言环境的无花果的代码被破坏了。
  15. 假设您可以删除变音符号以获取基本 ASCII 字母的代码是邪恶的、静止的、损坏的、脑损伤的、错误的和死刑的正当理由。
  16. 假设变音符号\p{Diacritic}和标记\p{Mark}是同一事物的代码被破坏了。
  17. 假设覆盖的代码与损坏的代码\p{GC=Dash_Punctuation}一样多。\p{Dash}
  18. 假设破折号、连字符和减号彼此相同,或者每一种只有一个的代码是错误的。
  19. 假设每个代码点占用不超过一个打印列的代码被破坏。
  20. 假定所有\p{Mark}字符占用零打印列的代码已损坏。
  21. 假设看起来相似字符相似的代码被破坏了。
  22. 假设看起来不相似的字符不相似的代码被破坏了。
  23. \X假设一行中只有一个可以匹配的代码点数量有限制的代码是错误的。
  24. 假设\X永远不能以\p{Mark}字符开头的代码是错误的。
  25. 假设\X永远不能容纳两个非\p{Mark}字符的代码是错误的。
  26. 假定它不能使用的代码"\x{FFFF}"是错误的。
  27. 假定需要两个 UTF-16(代理)代码单元的非 BMP 代码点的代码将编码为两个单独的 UTF-8 字符,每个代码单元一个,这是错误的。它没有:它编码为单个代码点。
  28. 如果将 BOM 放在生成的 UTF-8 的开头,则从带有前导 BOM 的 UTF-16 或 UTF-32 转码为 UTF-8 的代码会被破坏。这太愚蠢了,工程师应该去掉他们的眼睑。
  29. 假设 CESU-8 是有效的 UTF 编码的代码是错误的。同样,认为将 U+0000 编码"\xC0\x80"为 UTF-8 的代码是错误的。这些人也应该接受眼睑治疗。
  30. 假设字符>总是指向右边和<总是指向左边的代码是错误的——因为它们实际上并没有。
  31. 假设您先输出 characterX然后再输出 character 的代码Y,那些将显示为XY错误的代码。有时他们不会。
  32. 假设 ASCII 足以正确书写英语的代码是愚蠢的、短视的、文盲的、破碎的、邪恶的和错误的。 砍掉他们的头!如果这看起来太极端了,我们可以妥协:从今以后,他们可能只用一只脚的大脚趾打字。(其余的将用胶带粘住。)
  33. 假设所有\p{Math}代码点都是可见字符的代码是错误的。
  34. 假设\w只包含字母、数字和下划线的代码是错误的。
  35. 假定是标点符号的代码是错误的^~
  36. 假设ü有变音符号的代码是错误的。
  37. 认为其中包含任何字母的代码是错误的。
  38. 相信的代码与被严重破坏的代码\p{InLatin}相同。\p{Latin}
  39. 认为\p{InLatin}几乎永远有用的代码几乎肯定是错误的。
  40. 认为$FIRST_LETTER作为某个字母表中的第一个字母和$LAST_LETTER同一字母表中的最后一个字母给出的代码,[${FIRST_LETTER}-${LAST_LETTER}]具有任何意义几乎总是完全破碎、错误和无意义的。
  41. 认为某人的名字只能包含某些字符的代码是愚蠢、冒犯和错误的。
  42. 试图将 Unicode 简化为 ASCII 的代码不仅是错误的,而且绝不应允许其肇事者再次从事编程工作。时期。我什至不肯定他们甚至应该被允许再次看到,因为到目前为止这显然对他们没有多大好处。
  43. 认为有某种方法可以假装文本文件编码不存在的代码是损坏且危险的。还不如把另一只眼睛也伸出来。
  44. 将未知字符转换为的代码?是损坏的、愚蠢的、脑残的,并且违反了标准建议,即不要这样做!RTFM 为什么不呢。
  45. 认为它可以可靠地猜测未标记文本文件的编码的代码是一种致命的傲慢和天真混合,只有宙斯的闪电才能解决。
  46. 认为您可以使用 printf宽度来填充和证明 Unicode 数据的代码是错误的。
  47. 相信一旦您成功创建了一个给定名称的文件,当您运行lsreaddir在其封闭目录上运行时,您实际上会发现使用您创建它的名称的文件是错误的、损坏的和错误的代码。不要对此感到惊讶!
  48. 认为 UTF-16 是固定宽度编码的代码是愚蠢的、损坏的和错误的。吊销他们的编程许可证。
  49. 处理来自一个平面的代码点与来自任何其他平面的代码点完全不同的代码实际上是错误的。回学校去。
  50. 认为像这样的东西/s/i只能匹配"S""s"损坏和错误的代码。你会感到惊讶。
  51. \PM\pM*用于查找字素簇而不是使用的代码\X已损坏且错误。
  52. 应该全心全意地鼓励想要回到 ASCII 世界的人们这样做,并且为了纪念他们的光荣升级,应该免费为他们提供一台预电动手动打字机,以满足他们所有的数据输入需求。发送给他们的消息应通过 ᴀʟʟᴄᴀᴘs 电报发送,每行 40 个字符,并由快递员亲自递送。停止。

我不知道你能得到比我写的更多的“ 中的默认 Unicode”。嗯,是的,我愿意:你也应该使用Unicode::Collateand Unicode::LineBreak。而且可能更多。

如您所见,您确实需要担心太多的 Unicode 问题,以至于永远不会存在“默认为 Unicode”之类的东西。

你会发现,就像我们在 5.8 中所做的那样,根本不可能将所有这些东西强加于从一开始就没有正确设计的代码上。你善意的自私刚刚毁了整个世界。

即使你这样做了,仍然存在需要大量思考才能正确解决的关键问题。没有可以翻转的开关。只有大脑,我的意思是真正的大脑,在这里就足够了。有很多东西你必须学习。以手动打字机为模,你根本不能希望在无知中偷偷溜走。这是 21ˢᵗ 世纪,你不能因为故意无知而希望 Unicode 消失。

你必须学习它。时期。“一切正常”永远不会那么容易,因为这将保证很多事情行不通——这使得永远有办法“让一切都正常”的假设无效。

您可能能够为极少数且非常有限的操作获得一些合理的默认值,但并非没有考虑比我认为的更多的事情。

举个例子,规范排序会引起一些真正的麻烦。 "\x{F5}" ‘õ’"o\x{303}" ‘õ’"o\x{303}\x{304}" ‘ȭ’"o\x{304}\x{303}" ‘ō̃’都应该匹配‘õ’,但你到底要怎么做呢?这比看起来要难,但这是您需要考虑的事情。

如果有一件事,我知道Perl,它就是它的unicode比特做了什么,而不是这样做,我保证你: ᴛʜᴇʀᴇᴛʜᴇʀᴇsɴᴏuɴɪᴄᴏᴅᴇɴɪᴄᴏᴅᴇᴍᴀɢɪᴄᴍᴀɢɪᴄʙᴜʟʟᴇᴛʙᴜʟʟᴇᴛ

你不能仅仅改变一些默认值就可以一帆风顺。确实,我运行 时PERL_UNICODE设置为"SA",但仅此而已,甚至主要用于命令行的东西。对于实际工作,我会经历上面列出的所有步骤,并且非常、非常小心。

2022-03-07