了解可变向量以及如何在 Haskell 中修改它们
Understanding mutable vectors and how to modify them in Haskell
我正在查看 JuicyPixels 中的一些源代码,可变向量让我感到困惑。在第 808 行的函数 generateMutableImage
中是
generateMutableImage :: forall m px. (Pixel px, PrimMonad m)
=> (Int -> Int -> px) -- ^ Generating function, with `x` and `y` params.
-> Int -- ^ Width in pixels
-> Int -- ^ Height in pixels
-> m (MutableImage (PrimState m) px)
{-# INLINE generateMutableImage #-}
generateMutableImage f w h = MutableImage w h `liftM` generated where
compCount = componentCount (undefined :: px)
generated = do
arr <- M.new (w * h * compCount)
let lineGenerator _ !y | y >= h = return ()
lineGenerator !lineIdx y = column lineIdx 0
where column !idx !x | x >= w = lineGenerator idx $ y + 1
column idx x = do
unsafeWritePixel arr idx $ f x y
column (idx + compCount) $ x + 1
lineGenerator 0 0
return arr
我以为generated
是m (Data.Vector.Storable.Mutable.STVector)
类型的,也知道是怎么声明的,但不知道怎么修改的。
我知道 unsafeWritePixel
负责这个,但它与 (PrimMonad m) => m
monad 有什么联系?
另外,!y
的!
是什么意思?这些是一些神奇的运算符或符号吗?
片段
let lineGenerator _ !y | y >= h = return ()
lineGenerator !lineIdx y = column lineIdx 0
where column !idx !x | x >= w = lineGenerator idx $ y + 1
column idx x = do
unsafeWritePixel arr idx $ f x y
column (idx + compCount) $ x + 1
lineGenerator 0 0
是一种循环,在向量中写入图像数据。
这是通过定义一个递归函数 lineGenerator
然后用 lineGenerator 0 0
中的起始值调用它来实现的。第一个参数是索引,它指的是我们正在写的向量的位置,并且在递归过程中经常递增。第二个参数表示图像的 y
坐标。两者都从 0
.
开始
让我们逐行详细评论:
let lineGenerator _ !y | y >= h = return ()
如果当前y
比高度>=
,我们停止循环。
lineGenerator !lineIdx y = column lineIdx 0
否则,填一行。让x
从0
.
开始
where column !idx !x | x >= w = lineGenerator idx $ y + 1
如果 x
已达到宽度 w
,我们填充了该行,因此通过递增 y
移动到下一个。
column idx x = do
否则,我们需要在当前行写入一个像素。
unsafeWritePixel arr idx $ f x y
写在当前索引处,根据x
和y
的当前值使用f x y
计算图像数据。
column (idx + compCount) $ x + 1
然后,移动到下一个像素,同时推进 x
坐标和矢量索引(这样我们就不会覆盖刚刚写入的像素)。
一切似乎都在 m
monad 中执行,这是必需的,因为我们需要改变向量。
您可以安全地忽略 !y
和其他变量中的 !
。这些被称为“bang patterns”,在这里用作优化,以确保像 x+1
这样的数值被立即计算出来,而不是保持未评估状态。 Haskell 默认情况下是懒惰的,除非现在需要,否则不会计算任何东西,但这可能会影响性能,因此有时程序员会使用 bang 模式手动禁用惰性。同样,在阅读代码时,您可以假装这些 !
刘海不存在。
我正在查看 JuicyPixels 中的一些源代码,可变向量让我感到困惑。在第 808 行的函数 generateMutableImage
中是
generateMutableImage :: forall m px. (Pixel px, PrimMonad m)
=> (Int -> Int -> px) -- ^ Generating function, with `x` and `y` params.
-> Int -- ^ Width in pixels
-> Int -- ^ Height in pixels
-> m (MutableImage (PrimState m) px)
{-# INLINE generateMutableImage #-}
generateMutableImage f w h = MutableImage w h `liftM` generated where
compCount = componentCount (undefined :: px)
generated = do
arr <- M.new (w * h * compCount)
let lineGenerator _ !y | y >= h = return ()
lineGenerator !lineIdx y = column lineIdx 0
where column !idx !x | x >= w = lineGenerator idx $ y + 1
column idx x = do
unsafeWritePixel arr idx $ f x y
column (idx + compCount) $ x + 1
lineGenerator 0 0
return arr
我以为generated
是m (Data.Vector.Storable.Mutable.STVector)
类型的,也知道是怎么声明的,但不知道怎么修改的。
我知道 unsafeWritePixel
负责这个,但它与 (PrimMonad m) => m
monad 有什么联系?
另外,!y
的!
是什么意思?这些是一些神奇的运算符或符号吗?
片段
let lineGenerator _ !y | y >= h = return ()
lineGenerator !lineIdx y = column lineIdx 0
where column !idx !x | x >= w = lineGenerator idx $ y + 1
column idx x = do
unsafeWritePixel arr idx $ f x y
column (idx + compCount) $ x + 1
lineGenerator 0 0
是一种循环,在向量中写入图像数据。
这是通过定义一个递归函数 lineGenerator
然后用 lineGenerator 0 0
中的起始值调用它来实现的。第一个参数是索引,它指的是我们正在写的向量的位置,并且在递归过程中经常递增。第二个参数表示图像的 y
坐标。两者都从 0
.
让我们逐行详细评论:
let lineGenerator _ !y | y >= h = return ()
如果当前y
比高度>=
,我们停止循环。
lineGenerator !lineIdx y = column lineIdx 0
否则,填一行。让x
从0
.
where column !idx !x | x >= w = lineGenerator idx $ y + 1
如果 x
已达到宽度 w
,我们填充了该行,因此通过递增 y
移动到下一个。
column idx x = do
否则,我们需要在当前行写入一个像素。
unsafeWritePixel arr idx $ f x y
写在当前索引处,根据x
和y
的当前值使用f x y
计算图像数据。
column (idx + compCount) $ x + 1
然后,移动到下一个像素,同时推进 x
坐标和矢量索引(这样我们就不会覆盖刚刚写入的像素)。
一切似乎都在 m
monad 中执行,这是必需的,因为我们需要改变向量。
您可以安全地忽略 !y
和其他变量中的 !
。这些被称为“bang patterns”,在这里用作优化,以确保像 x+1
这样的数值被立即计算出来,而不是保持未评估状态。 Haskell 默认情况下是懒惰的,除非现在需要,否则不会计算任何东西,但这可能会影响性能,因此有时程序员会使用 bang 模式手动禁用惰性。同样,在阅读代码时,您可以假装这些 !
刘海不存在。