我有一个小的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
上有没有好的教程?
答案 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
。
但是,这会导致性能下降,或者您需要使用“不安全”变体。既然你已经在做不安全的写作,那可能就行了。