Haskell 字符串的终极指南·Hasufell 的博客

这是一篇关于 Haskell 中字符串类型的指南,旨在帮助 Haskellers 提高对字符串类型的理解,包括初学者和经验丰富的开发者。它还提供了一个快速参考/备忘单,用于决定在给定情况下使用哪种字符串类型。

一、动机
2022 年作者实现了Abstract FilePath 提案,导致了诸如OsString等新的字符串类型的出现。同时,作者还在Core Libraries Committee中,参与了基础 API 的监督工作,其中经常讨论字符串类型的问题。由于缺乏全面的文档,作者希望通过这篇博客文章来填补一些文档空白,并解释新引入的类型以及为什么作者认为我们没有太多的字符串类型

二、Unicode 相关

  • Unicode 标准:是一个识别和编码可见“字符”的标准,支持世界上所有主要的书写系统。术语“字符”含义模糊,重点关注核心概念,如 Unicode Code Point、UTF-32、UTF-16、UTF-8 等。
  • Unicode Code Point:通过数值编码单个字符,范围从 0 到 10FFFF,形式为U+0000U+10FFFF,是 ASCII 字符集的扩展,能表达中文和其他非拉丁字符,有些字符由多个 Code Point 表示。
  • UTF-32:固定长度为 32 位,能表示所有 Unicode 值,但浪费空间,不与 ASCII 兼容。
  • UTF-16:可变宽度字符编码,常用在 Windows 上,U+0000 到 U+FFFF 直接用 2 字节表示,U+10000 到 U+10FFFF 用 surrogate 对表示。
  • Unicode Scalar Values:Haskell 的Char类型本质上是一个 Code Point,能表示 UTF-16 中的 surrogate。Unicode 标准还定义了 Unicode Scalar Values 的概念,即除了 high-surrogate 和 low-surrogate 之外的任何 Unicode 代码点。
  • UTF-8:可变宽度字符编码,常用于 Web APIs 和 Unix 系统,根据代码点范围用 1 到 4 个字节表示,与 ASCII 兼容,UTF-8 中 surrogate 范围的代码点被视为无效字节序列,只表达 Unicode Scalar Values。

三、Haskell 中的字符串类型

  • String(不推荐):在 Haskell 标准中定义,是一个字符列表,作为中间表示在编码转换时有用,但对于大型文本效率低下,容易让用户困惑,不适合某些转换,只应用于小项目和原型。
  • Text:用于人类可读的 Unicode 文本,内部使用 UTF-8 或 UTF-16 编码,有严格和懒惰两种变体。严格Text使用A.Array存储字节数组,允许高效切片;懒惰Text结构类似列表,可用于流式处理和增量处理。Text不允许表示 surrogate,会将无效值转换为U+FFFD
  • ShortText:用于大量小文本序列,是text-short包的一部分,定义为newtype ShortText = ShortText ShortByteString,数据保证为有效 UTF-8,但不允许与text-icu包一起使用,也不适合高效切片等操作。
  • ByteString:低级别字节序列类型,从bytestring包中引入,使用固定内存,与 FFI 交互时更方便,效率高但缺乏文本处理功能,有严格和懒惰两种变体,严格ByteString使用ForeignPtr Word8存储字节数组,切片操作高效,懒惰ByteString结构类似懒惰Text
  • ShortByteStringbytestring包中的类型,与ByteString API 相似,通常使用非固定内存,避免堆碎片,可用于 Unix 文件路径等,但不支持切片,没有懒惰变体。
  • Bytesbyteslice包中的类型,类似于ShortByteString,具有 0 拷贝切片功能,可构造为固定或非固定字节序列,有Chunks变体,用于避免ByteArray追加的开销。
  • OsString、PosixString 和 WindowsString:相对较新的类型,用于抽象平台差异和操作系统 API 的编码,在处理文件路径等操作时更安全、类型更安全,内部使用Word8Word16数组表示。
  • OsPath、PosixPath 和 WindowsPath:与OsString等对应的文件路径类型,是filepath包的一部分,是类型同义词。
  • CString 和 CStringLenbase中的低级 FFI 类型,用于与 C 代码交互。

四、Lazy 与 Strict

  • Lazy:可以流式处理和增量处理,可能在常量空间中运行,允许垃圾回收器在切片后清理未使用的块,可表示无限数据流,时间复杂度稍高,可与懒惰 IO 一起使用。
  • Strict:在时间复杂度上最有效,始终强制进入内存,开销比懒惰类型小。

五、Slicable 与 non-slicable

  • Slicable:如Text,切片时不需要复制数据,节省memcpy操作,但会增加内存开销(携带两个Int字段),适合处理大型字符串或需要大量切片的情况。
  • non-slicable:如ShortText,切片时会创建新的字节数组并复制数据,节省内存开销和一些运行时间接性,但不适合处理大型字符串或需要大量切片的情况。

六、Pinned vs unpinned

  • Pinned memory:不能被垃圾回收器移动,用于与外国代码交互时避免复制数据,但会导致内存碎片,特别是对于大量小的固定内存数据。
  • Unpinned memory:可以被垃圾回收器移动,避免内存碎片,但在与外国代码交互时需要复制数据。

七、Construction(构造字符串的方式)

  • String literals:在 Haskell 文件中写入的字符串字面量将被编译器转换为[Char]
  • String Classes:如IsString类,允许将String转换为其他兼容类型,但存在一些问题,如Textpack函数不处理 surrogate,ByteString/ShortByteString的实例会截断为 8 位。
  • OverloadedStrings:语言扩展,允许字符串字面量用于所有具有IsString实例的类型,但会使类型推断更困难,并且存在IsString类的问题。
  • QuasiQuoters:使用 Template Haskell 在编译时运行的表达式,用于更严格地验证字符串字面量,避免无效的 Unicode 序列,但会增加编译时间,并且可能与工具链存在问题。

八、Conversions(转换字符串类型)

  • String到其他类型:需要指定编码,如toTextString转换为TexttoByteStringString转换为ByteString等。
  • Text到其他类型:可复用处理String的 API,如toStringText转换为String等。
  • ByteString到其他类型:需要提供编码进行解码,如toStringByteString转换为String等,将ByteString转换为OsString较复杂,取决于字节序列的来源和用途。
  • ShortByteString到其他类型:与ByteString的转换类似,需要指定编码等。
  • OsString到其他类型:OsString提供了多种解码和编码函数,如encodeUtf/decodeUtf等,用于在不同平台和编码之间进行转换。

九、To JSON

  • TextString已有ToJSON实例,因为它们是 Unicode 且 JSON 要求 UTF-8。
  • ByteStringShortByteStringOsString转换为 JSON 较复杂,取决于具体用途,如可转换为String(假设 UTF-8 或 UTF-16)、base64 编码的String[Word8]等。

十、A word on lazy IO

  • 一些命名包的懒惰变体 API 用于读取和写入文件,但懒惰 IO 存在问题,如文件可能被锁定、操作系统文件描述符限制等,最好使用适当的流式库。

十一、Streaming(流式处理)

  • 有许多流行的流式库,如conduitstreamingstreamlypipes等,可解决懒惰 IO 问题和[Char]类型的效率问题,提供更高效的处理方式。
  • streamly为例,提供了decodeUtf8encodeUtf8等函数用于处理 Unicode 字符流,示例程序展示了如何使用streamly读取文件的最后一个 Unicode 字符。

十二、Reflection(反思)

  • What we should know:理解了 Unicode 的相关概念,包括字符编码、文本编码、不同的转换格式及其优缺点、Char类型的问题、grapheme clusters 的概念等,还了解了不同字符串类型的总结和各种字符串操作。
  • Too many Strings:虽然有人认为 Haskell 有太多字符串类型,但实际上不同类型都有其特性和权衡,懒惰变体可能被流式库替代,但它们仍有其用途,新的字符串类型的创建具有挑战性,但可以通过 newtype 来实现。
  • What are we missing:缺少 Unicode Scalar Values 和 Grapheme Clusters 类型,text-icu包有相关 API 但不直观,也没有良好的基于 base 的流式解决方案,作者的下一个项目可能是强类型文件路径。

十三、Special thanks to(特别感谢)
感谢了 Andrew Lelechenko、Jonathan Knowles、Mike Pilgrem、John Ericson 以及 streamly 维护者和其他相关包的维护者。

十四、Links and relevant stuff(链接和相关内容)

阅读 37
0 条评论