Haskell中如何以Unicode感知的方式对文本字符串应用tail/tails操作?
你遇到的这个问题其实挺典型的——就拿那个⚠️符号来说,它本质是两个Unicode码点的组合:U+26A0(基础的警告符号)加上U+FE0F(变体选择器),在Haskell里我们会写成"\x26a0\xfe0f"。但麻烦的是,Haskell标准库的take/drop/tail/head这类列表操作,还有Data.List.tails,根本不知道这两个码点是必须绑定在一起的:
λ> Data.List.tails "\x26a0\xfe0f" ["\9888\65039","\65039",""]
你看,它直接把第二个码点单独拆出来了,这显然不是我们想要的结果。
一开始我也和你一样,看了Haskell字符串的相关指南后,觉得Data.Text或者Data.Text.Lazy应该能解决这个问题,毕竟它们是专门优化的文本处理库。但实际在GHCi里试了才发现,情况并没有好转:
λ> :set -XOverloadedStrings λ> import qualified Data.Text as T λ> import qualified Data.Text.Lazy as LT λ> LT.tails $ ("\x26a0\xfe0f" :: LT.Text) ["\9888\65039","\65039",""] λ> T.tails $ ("\x26a0\xfe0f" :: T.Text) ["\9888\65039","\65039",""]
后来回头看那篇指南里的「我们缺少什么」部分,才反应过来问题所在:目前Haskell的标准库和常用文本库,都没有专门针对**字形簇(Grapheme Clusters)**的类型支持——而字形簇正是把那些应该作为一个整体显示的码点组合起来的单位,比如这个带变体的警告符号就是一个典型的字形簇。
不过指南里也提到了一个可行的方案:text-icu这个第三方包。它提供了基于Unicode字形边界拆分的API,能够正确识别哪些码点属于同一个字形簇,这样在做类似tails的操作时,就不会把本该绑定的码点拆开了。
简单来说,如果你想让tail/tails这类操作真正做到Unicode感知、不破坏字形簇,目前只能借助text-icu这样的第三方库——标准库和text包本身暂时还没有内置这个能力。
备注:内容来源于stack exchange,提问作者Enlico

