3

我有一个小的 Haskell 函数,它应该接受一个STUArray,修改一些元素,然后返回更改后的数组。它将从在ST s (STUArray s Int Word32)monad 中工作的另一个函数调用。PBKDF2它是我正在尝试编写的快速函数的一部分。此函数对固定大小的消息(160 位)进行 SHA-1 填充。

这是我的代码:

padFixed :: STUArray s Int Word32 -> ST s (STUArray s Int Word32)
padFixed block = do
  unsafeWrite block 5 0x80000000
  unsafeWrite block 15 160
  return block

该数组将包含前一次 SHA-1 运行的 20 个字节,加上 44 个字节的零。它将根据RFC 3174添加所需的填充。

我该如何重写它,以便将数组从 monad 中“取出”,对其进行处理,然后将其放回原处?签名应该是padFixed :: ST s (STUArray s Int Word32),没有block参数。

这可能吗?我在库中找不到任何可以让我从 monad 中提取数组的函数,但也许我错过了一些东西。

有什么好的教程STArray吗?

4

3 回答 3

4

不,这是不可能的;ST没有那些语义。monad 是ST s,而不是ST s (STUArray s a)ST s只是一个用于跟踪可变状态的单子;您选择在单个ST区域内分配和使用哪些结构取决于您。如果你有一堆计算都在同一个上运行STUArray,你可以使用ReaderT

type Hasher s = ReaderT (STUArray s Int Word32) (ST s)

padFixed :: Hasher ()
padFixed = do
  block <- ask
  unsafeWrite block 5  0x80000000
  unsafeWrite block 15 160

Reader rmonad 只是一个包装器r ->;类型的值Reader r a只是一个函数r -> a。这本质上是一种a在访问类型值的同时进行计算的方法rReaderT rmonad 转换器只允许您提供对r任意 monad 计算的类型变量的访问;因此,ReaderT (STUArray s Int Word32) (ST s)是一个ST s可以访问某个数组的计算。请注意,您不需要从padFixed;返回数组。monad 绑定将处理所有这些。

这写起来会有点麻烦,因为我们必须继续ask为数组做准备。幸运的是,我们可以编写一些组合器来为我们处理这个问题:

{-# LANGUAGE RankNTypes, GeneralizedNewtypeDeriving #-}

import Data.Word
import Control.Applicative
import Control.Monad.Reader
import Control.Monad.ST
import Data.Array.ST (STUArray, runSTUArray)
import qualified Data.Array.Base as A
import Data.Array.Unboxed (UArray)

newtype Hasher s a =
  Hasher { getHasher :: ReaderT (STUArray s Int Word32) (ST s) a }
  deriving (Functor, Applicative, Monad, MonadReader (A.STUArray s Int Word32))

hasherToST :: Hasher s () -> (Int,Int) -> ST s (STUArray s Int Word32)
hasherToST (Hasher r) bounds = do
  block <- A.newArray bounds 0
  runReaderT r block
  return block

runHasher :: (forall s. Hasher s ()) -> (Int,Int) -> UArray Int Word32
runHasher h bounds = runSTUArray $ hasherToST h bounds

-- Perhaps private to this module, perhaps not
liftST :: ST s a -> Hasher s a
liftST = Hasher . lift

----- We can lift the functions which act on an STUArray -----

getBounds :: Hasher s (Int,Int)
getBounds = liftST . A.getBounds =<< ask

-- I'd recommend against removing the `unsafe` from the name; this function
-- could segfault, after all.
unsafeReadBlock :: Int -> Hasher s Word32
unsafeReadBlock i = do
  block <- ask
  liftST $ A.unsafeRead block i

unsafeWriteBlock :: Int -> Word32 -> Hasher s ()
unsafeWriteBlock i x = do
  block <- ask
  liftST $ A.unsafeWrite block i x

----- And then, perhaps in a separate module: -----

padFixed :: Hasher s ()
padFixed = do
  unsafeWriteBlock 5  0x80000000
  unsafeWriteBlock 15 160

(请注意,我无法在 内hasherToST内联runHasher,可能是因为更高级别的类型阻塞了推理。)

基本上,我们将 a 包装ReaderT (STUArray s Int Word32) (ST s)成 anewtype而不是类型同义词,并提升一些基本的数组原语以在始终可用的块上工作。如果您不想要,您甚至不需要MonadReaderHasher类型派生,只要您解除所有必要的功能。但是一旦你这样做了,你的散列代码就可以隐式地讨论数组。

于 2013-10-21T21:03:59.613 回答
0

不,你很困惑;那是不可能的。将STUArray s i e其视为指向内存块开头的指针。您必须将该指针传递给需要修改该内存块的任何内容;你不能凭空变出它。

但是你不需要退货。大概调用者已经有了指针。

于 2013-10-21T20:46:03.800 回答
-1

您可以使用freezeandthaw函数UArray.

但是,这会导致性能损失,或者您需要使用“不安全”变体。既然你已经在做不安全的写作,那可能没关系。

于 2013-10-21T20:45:50.687 回答