与STUArray一起使用的Haskell函数

时间:2013-10-21 19:50:10

标签: haskell state mutable

我有一个小的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上有没有好的教程?

3 个答案:

答案 0 :(得分: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 r monad只是r ->的包装;类型Reader r a的值只是一个函数r -> a。这实际上是一种在访问类型a的值时计算r的方法。 ReaderT r monad转换器只允许您为任意monadic计算提供对r类型变量的访问;因此,ReaderT (STUArray s Int Word32) (ST s)ST s计算,可以访问某些数组。请注意,您无需从padFixed返回数组; monad bind将处理所有这些。

写这个会有点痛苦,因为我们必须保持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,可能是因为更高级别的类型阻止推断。)

基本上,我们将ReaderT (STUArray s Int Word32) (ST s)包装成newtype而不是类型同义词,并提升一些基本数组基元以处理始终可用的块。如果您不想要,您甚至不需要为MonadReader类型派生Hasher,只要您取消所有必要的功能即可。但是一旦你完成了这个,你的哈希代码就会隐含地讨论这个数组。

答案 1 :(得分:0)

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

但你不需要退货。据推测,调用者已经有了指针。

答案 2 :(得分:-1)

您可以使用freeze and thaw functions转换为UArray

但是,这会导致性能下降,或者您需要使用“不安全”变体。既然你已经在做不安全的写作,那可能就行了。