Haskell 提高 I/O 效率的技巧及资源控制。
二进制 I/O
Data.ByteString :定义严格求值的 ByteString 类型,将一串二进制数据或文本数据用一个数组表示。适合不在意内存限制并要求随机存取的情况。
Data.ByteString.Lazy:提供了 ByteString 的惰性类型,将一串数据分块组成列表,每块大小 64KB 。该方式惰性执行,对于体积较大的数据,惰性的 ByteString 类型会更好,其块大小针对现代 CPU L1缓存调整过,已处理过的、不会再被使用的流数据会被垃圾处理器快速回收。
- 以上两种类型均提供了和
String 类型兼容的接口函数,但元素类型为字节 Word8,该类型在 Data.Word 模块中声明。
- 可使用
pack 函数将字节数组装载为 ByteString:L.pack :: [Word.Word8] -> L.ByteString。
ByteString 库提供了两个功能有限的 I/O 功能模块:Data.ByteString.Char8 和 Data.ByteString.Lazy.Char8,其中的函数仅适用于单字节大小的 Char 值(ASCII和某些欧洲字符集,大于 255 会被截断)。这两个模块提供了较多方便的函数,如 readInt、split 等。
正则
- Haskell 的正则通过
Text.Regex.Posix 模块提供,其中 =~ 操作符是正则表达式匹配函数。其参数和返回值都使用了类型类,第一个参数是要被匹配的文本,第二个参数是正则表达式,每个参数都可以用为 String 或者 ByteString 类型。其返回值是多态的,但文本匹配的结果必须和被匹配的字符串一致,我们可以将 String 和 ByteString 组合,但结果类型必须和被匹配字符串一样。正则表达式可以使 String 或者 ByteString,没有限制。根据返回类型签名不同,返回结果也有区别:
Bool:字符串和正则式是否匹配
Int:正则式在字符串中成功匹配的次数
(Int, Int):格式为(首次匹配在字符串中的偏移量,首次匹配结果的长度),偏移量为 -1 时表示字符串和正则式不匹配
[(Int, Int)]:得到所有匹配子串的(偏移量,匹配长度),列表为空代表无匹配
String:得到第一个匹配的子串,或者无匹配的空字符串
[[String]]:返回由所有匹配的字符串组成的列表
(String, String, String):匹配成功时为(首次匹配之前的部分,首次匹配的子串,首次匹配之后的部分),匹配失败时为(整个字符串,””,””)
(String, String, String, [String]):前三个元素和三元组相同,第四个元素是包含了模式中所有分组的列表
- 正则函数可配合其他函数如
getAllTextMatchs 来获取更多结果:
1 2
| ghci> ("foo buot" =~ "(oo|uo)") :: [String] ["oo", "uo"]
|
文件系统路径
- Haskell 的文件系统处理函数主要由
System.Directory 提供,如 doesDirectoryExist、doesFileExist、getCurrentDirectory、getDirectoryContents 等。
System.FilePath 主要处理文件路径,由两个模块构成:System.FilePath.Posix 和 System.FilePath.Windows,二者接口完全相同,适配平台不同。包含函数如:
getSearchPath:获得 $PATH 环境变量内容
</>:将两个字符串用 / 合为一个路径
<.>:将后缀名结合,等价于 addExtension
-<.>:去掉后缀名并添加一个新的后缀名,等价于 replaceExtension
dropTrailingPathSeparator:去掉文件路径后的分隔符,如 /
splitFileName:返回将路径切割为父级目录和文件名的二元组
常见 I/O 异常处理
- 异常处理的几个常用函数包含在
Control.Exception 中。
handle :: (Exception -> IO a) -> IO a -> IO a 接收的第一个参数是一个函数,该函数接受一个异常值并且返回 IO Monad,第二个参数是可能抛出异常的 IO Monad。当第二个 IO Monad 执行出现异常时,作为 handle 第一个参数的函数会接收产生的异常值,并返回自己的 IO Monad;当第二个参数执行无异常时, handle 返回值与第二个参数相同。
- 在
handle 的使用中可使用 const 忽略传入的异常。const 接收两个参数,无论第二个参数是什么都返回第一个参数:
1 2
| handle (const (return [])) (code_may_cause_exception) const (return []) :: Monad m => b -> m [t]
|
- 也可以使用
finally 捕获异常,其类型签名为 finally:: IO a-> IO b -> IO a,无论第一个 IO Monad 成功或失败,第二个 IO Monad 都会执行。
bracket 可以看作 Haskell 中的 defer:如果你试图获取一个资源,对该资源做一些操作,并想在操作结束后释放这个资源,则可以使用 bracket 来保证最终资源的释放。
bracket 接收三个参数,第一个参数用于资源的获取,它的返回值会传给第二、三个参数,而第二个参数对应资源的释放,第三个参数为对资源的操作,它的返回值也是整个 bracket 函数的返回值。例如:
1 2 3 4 5
| bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c bracket (openFile "filename" ReadMode) (hClose) (\fileHandle -> do { ... })
|
- 当不需要获取第一个参数的返回值时,或这几个操作之间并无关系,使用
bracket_ :: IO a -> IO b -> IO c -> IO c 替代 bracket。
- 如果仅希望释放操作在执行操作出现异常时调用,则使用
bracketOnError:: IO a -> (a -> IO b) -> (a -> IO c) -> IO c。
原创作品,允许转载,转载时无需告知,但请务必以超链接形式标明文章原始出处(http://blog.forec.cn/2016/11/30/efficient-haskell-io/) 、作者信息(Forec)和本声明。