返回文章列表

文章

Get Programming with Haskell-23

在阅读完第 23 课后,你将能够: • 使用 Text 类型进行更高效的文本处理。 • 使用语言扩展改变 Haskell 的行为。 • 使用常见的文本函数进行编程。 • 使用 Text 类型正确处理 Unicode 文本。

目录
  1. 处理文本和Unicode
  2. 23.1 Text 类型
  3. **23.1.1 何时使用 Text vs. **String
  4. **23.2 使用 **Data.Text
  5. 23.2.1 OverloadedStrings 和 Haskell 扩展
  6. 23.3 Text 和 Unicode
  7. 23.3.1 搜索梵文
  8. 23.4 Text I/O
  9. 总结

处理文本和Unicode#

到目前为止,本书中你已经大量使用了 String 类型。在前一课中,你甚至看到可以将 I/O 流视为 Char 类型的惰性列表,或者一个 StringString 在帮助你探索本书中的许多主题方面非常有用。不幸的是,String 有一个巨大的问题:它可能非常低效。 从哲学角度来看,没有什么比将编程中最重要的类型之一表示为 Haskell 中最基础的数据结构之一——列表——更完美了。问题是,列表并不是存储大量字符串处理数据的理想数据结构。Haskell 性能的细节超出了本书的范围,但可以说,将字符串实现为字符的链表在时间和空间上都是不必要的昂贵。 在本课中,你将了解一种新类型 Text。你将探索如何用 Text 替换 String 以进行更高效的文本处理。然后,你将学习 StringText 共有的用于处理文本的函数。最后,你将通过构建一个可以突出显示搜索文本的函数(即使在梵语中也可以!)来了解 Text 如何处理 Unicode。

23.1 Text 类型#

对于实用和商业 Haskell 编程,处理文本数据的首选类型是 Text 类型。Text 类型可以在 Data.Text 模块中找到。在实践中,Data.Text 几乎总是通过使用单个字母(通常是 T)进行限定导入:

import qualified Data.Text as T

String 不同,Text 在底层实现为数组。这使得许多字符串操作更快,并且内存效率更高。TextString 的另一个主要区别是 Text 不使用惰性求值。惰性求值在前一课中被证明是有用的,但在许多实际应用中,它可能导致性能问题。如果确实需要惰性文本,可以使用 Data.Text.Lazy,它具有与 Data.Text 相同的接口。

**23.1.1 何时使用 Text vs. **String#

在商业 Haskell 社区中,Data.TextString 更受推崇。Haskell 社区的一些成员认为,由于对 String 的严重依赖,对于任何实际应用都应该抛弃标准的 Prelude。然而,在学习 Haskell 时,String 由于两个原因仍然有用。首先,正如前面提到的,许多基本的字符串实用程序都内置在标准的 Prelude 中。其次,列表对于 Haskell 来说就像数组对于 C 语言一样。Haskell 中的许多概念都可以通过列表很好地演示,而字符串是很有用的列表。 出于学习目的,可以随意继续使用 String。但是对于练习之外的任何事情,请尽可能多地使用 Data.Text。本书的许多地方你将继续使用 String,但会开始更频繁地使用 Data.Text

**23.2 使用 **Data.Text#

你需要做的第一件事是学习如何使用 Text 类型。Data.Text 有两个函数,packunpack,可以用于在 StringText 之间进行转换。通过它们的类型签名可以很容易地确定哪个函数做什么:

T.pack :: String -> Text
T.unpack :: Text -> String

T.pack 函数接受一个普通的 Haskell String 并将其转换为更高效的 Text 类型。T.unpack 函数执行相反的操作,将 Text 值转换回标准的 Haskell String。记住,由于我们使用了限定导入 import qualified Data.Text as T,所以在代码中使用这两个函数时,需要在函数名前加上 T. 前缀,例如 T.packT.unpack。 翻译:以下是一些将 String 转换为 Text 以及将 Text 转换回 String 的示例。

firstWord :: String
firstWord = "pessimism"

secondWord :: T.Text
secondWord = T.pack firstWord

thirdWord :: String
thirdWord = T.unpack secondWord

重要的是要注意,转换并非计算上很廉价,因为你必须遍历整个字符串。应避免在 TextString 之间来回转换。

23.2.1 OverloadedStrings 和 Haskell 扩展#

关于 T.Text 令人恼火的一点是,以下代码会抛出一个错误。

myWord :: T.Text
myWord = "dog"

你得到的错误信息如下:

Couldn't match expected type 'T.Text' with actual type '[Char]'

发生此错误是因为字面量 "dog" 是一个 String。这尤其令人恼火,因为你使用数字类型时不会遇到这个问题。例如,考虑以下数字。

myNum1 :: Int 
myNum1 = 3
myNum2 :: Integer 
myNum2 = 3
myNum3 :: Double 
myNum3 = 3

即使你对三种不同的类型使用了相同的字面量 3,这段代码也能很好地编译。 显然,无论 Haskell 多么强大,这都不是你能通过巧妙的编码解决的问题。要解决这个问题,你需要一种从根本上改变 GHCi 读取文件方式的方法。令人惊讶的是,有一个简单的解决方案!GHCi 允许你使用语言扩展来改变 Haskell 本身的工作方式。你将要使用的特定扩展称为 OverloadedStrings。 有两种使用语言扩展的方法。第一种是在使用 GHCi 编译时使用它。为此,请使用 -X 标志后跟扩展名称。对于名为 text.hs 的程序,它看起来像这样:

$ ghc text.hs -XOverloadedStrings

这也可以作为 GHCi 的参数使用,以通过使用语言扩展启动 GHCi 的实例。 问题是,使用你的代码的人(这个人可能是你)可能不记得使用这个标志。一种首选方法是使用 LANGUAGE 编译指示(pragma)。 该编译指示看起来像这样:

{-# LANGUAGE <扩展名称> #-}

这是一个 text.hs 文件,它允许你对 Text 类型使用字面量值。

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text as T 
aWord :: T.Text 
aWord = "Cheese"
main :: IO ()
main = do
	print aWord