8

这段 C 代码在概念上可以描述为创建一个与输入数组相同但以 1 作为第一个元素的新数组:

int* retire_and_update(int* arr) {
    arr[0] = 1;
    return arr;
}

这是一个纯函数(wink wink nudge nudge),只要不对输入数组及其元素进行进一步引用。C 类型系统不会为我们强制执行,但原则上似乎可以强制执行。

gcc 生成的代码简单高效:

retire_and_update:
    movq    %rdi, %rax
    movl    $1, (%rdi)
    ret

我们的函数通过在恒定时间内创建一个全新的数组并且不使用额外的内存来实现看似不可能的事情。好的。可以编写一个具有类似数组输入和输出的 Haskell 函数,并且可以用类似的代码有效地实现吗?有没有办法表达“这是对该变量的最后引用”,以便纯函数可以在幕后蚕食变量?

如果函数被内联,那么这里不需要发生任何有趣的事情,所以我们假设调用者和函数将分别编译。

4

1 回答 1

6

尽管与您描述ST monadSTUArray完全一样,但实际上您可以使用. 因此,您的代码模型可能类似于:

import Control.Monad (forM_)
import Control.Monad.ST (ST)
import Data.Array.Unboxed (UArray)
import Data.Array.ST (STUArray, newArray, readArray, writeArray, runSTUArray)

retire_and_update :: STUArray s Int Int -> ST s (STUArray s Int Int)
retire_and_update arr = do
    writeArray arr 0 1
    return arr

如果您有另一个函数可以就地改变数组,例如:

mutate_inplace :: STUArray s Int Int -> Int -> ST s ()
mutate_inplace arr size = do
    forM_ [2..size - 1] $ \i -> do
        a <- readArray arr (i - 2)
        b <- readArray arr (i - 1)
        writeArray arr i (a + b)

您可以将两个不纯函数绑定在一起,并使用以下方法在纯函数中调用它们runSTUArray

run :: Int -> UArray Int Int
run size = runSTUArray $ do
    arr <- newArray (0, size - 1) 0
    retire_and_update arr
    mutate_inplace arr size
    return arr

请注意,它run保持纯净,并且返回数组的早期版本不会泄漏到任何地方

\> run 8
array (0,7) [(0,1),(1,0),(2,1),(3,1),(4,2),(5,3),(6,5),(7,8)]
于 2015-11-20T13:28:33.963 回答