【问题标题】:Understanding mutable vectors and how to modify them in Haskell了解可变向量以及如何在 Haskell 中修改它们
【发布时间】:2021-02-24 11:15:20
【问题描述】:

我正在查看来自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

我认为generatedm (Data.Vector.Storable.Mutable.STVector) 类型并且理解它是如何声明的,但我不知道它是如何修改的。

我知道 unsafeWritePixel 是负责这件事的人,但它与 (PrimMonad m) =&gt; m monad 有什么关系?

另外,!y! 是什么意思?这些是一些神奇的运算符或符号吗?

【问题讨论】:

标签: haskell vector monads


【解决方案1】:

sn-p

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 比高度为&gt;=,我们停止循环。

    lineGenerator !lineIdx y = column lineIdx 0

否则,填充一行。让x0开始。

      where column !idx !x | x >= w = lineGenerator idx $ y + 1

如果x 已达到w 的宽度,我们填充了该行,因此通过递增y 继续下一行。

            column idx x = do

否则,我们需要在当前行写入一个像素。

                unsafeWritePixel arr idx $ f x y

写在当前索引处,使用f x y根据xy的当前值计算图像数据。

                column (idx + compCount) $ x + 1

然后,移动到下一个像素,同时推进x 坐标和矢量索引(这样我们就不会覆盖我们刚刚写入的像素)。

一切似乎都在m monad 中执行,这是必需的,因为我们需要对向量进行变异。

您可以放心地忽略!y 和其他变量中的!。这些被称为“爆炸模式”,在这里用作优化,以确保立即计算像x+1 这样的数值,而不是保持未计算的状态。 Haskell 默认是惰性的,除非现在需要,否则不会计算任何东西,但这可能会影响性能,因此有时程序员使用 bang 模式手动禁用惰性。同样,在阅读代码时,您可以假装这些! 刘海不存在。

【讨论】:

  • 非常感谢您的回答!我收集到我可以简单地使用unsafeWritePixel,而不必担心向量实际上是如何更新的,也许在(PrimMonad m) =&gt; m
  • @Chien 我不明白您要做什么,但是是的,您可以在任何有效的index 上使用unsafeWritePixel 来更新像素。请务必在向量范围内提供有效的index,否则程序行为是未定义的(这就是它被称为“不安全”的原因)。
  • 我现在清楚了,谢谢你的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
  • 2022-01-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多