其他人已经解决了执行字节操作的问题,所以我将专注于您问题的另一半:在ByteString 中选择和更新特定字节。让我们开始使用更熟悉的界面实现普通列表的操作:
onNth :: Int -> (a -> a) -> ([a] -> [a])
onNth n f xs = case splitAt n xs of
(beginning, x:ending) -> beginning ++ f x : ending
_ -> xs -- happens when n is out-of-bounds
您可以等效地使用take 和drop 而不是splitAt 来实现这一点。现在,我们如何将其翻译为在ByteStrings 上工作?好吧,ByteString 接口提供了take、drop、splitAt、append 和cons;我们唯一没有得到的是我们在上面x:ending 部分中所做的模式匹配。幸运的是,ByteString 确实提供了类似的功能:
uncons :: ByteString -> Maybe (Word8, ByteString)
因此,使用它,我们可以编写一个适用于 ByteStrings 的新 onNth 函数:
second :: (b -> c) -> (a, b) -> (a, c)
second f (a, b) = (a, f b)
onNth :: Int -> (Word8 -> Word8) -> (ByteString -> ByteString)
onNth n f bs = case second uncons (splitAt n bs) of
(beginning, Just (x, ending)) -> append beginning (cons (f x) ending)
_ -> bs -- again, for out-of-bounds cases
最后,我们可以讨论应该使用什么函数作为上面的f :: Word8 -> Word8 参数。尽管您在上面谈论文本,但我要指出,无论如何您都不应该将ByteString 用于文本(ByteStrings 是字节序列,而不是Chars 序列)。因此,如果您选择使用ByteString,您必须谈论的是字节,而不是文本。 ;-)
因此,您真的想问一个将 byte 减一的函数,大概是在边界上环绕。 subtract 1 正是这样的函数,所以要将pack [97, 97, 97, 97, 97] 转换为pack [97, 97, 96, 97, 97],你可以写成onNth 2 (subtract 1)。读起来几乎像英语!