分段可变状态的monad

问题描述 投票:5回答:1

我知道普通的State是如何工作的(编辑:显然不是!)。

如果我需要创建一个数组,并且不方便一次创建整个数组,我可以创建一个STArray,填充它,然后冻结并返回一个普通的不可变数组给用户。

现在假设我需要同时创建两个不同类型的数组。

更一般地说,我可能想要创建一个具有可变节点的任意图形,逐个节点地修改它,就像我将逐个单元地修改STArray一样,然后立即冻结整个图形并返回正常的不可变数据。

我不想诉诸IOArrays或IO monad中的任何东西。我有什么选择?

haskell state-monad
1个回答
5
投票

这里有一些选择。

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module TestSTArray where

import Control.Monad.ST
import Data.Array.ST
import Data.Array
import Data.Array.MArray

-- not needed until later on    
import GHC.Arr (unsafeFreezeSTArray)

基本的,安全的方法是冻结。但这会导致副本。

test2Safe :: (Array Int Char, Array Int Bool)
test2Safe = runST $ do
   a1 <- newArray (0,9) 'A' :: ST s (STArray s Int Char)
   a2 <- newArray (0,9) False :: ST s (STArray s Int Bool)
   writeArray a1 5 'B'
   x <- readArray a2 6
   writeArray a1 7 (if x then 'X' else 'Y')
   writeArray a2 5 True
   arr1 <- freeze a1
   arr2 <- freeze a2
   return (arr1, arr2)

风险更大,但可能仍然安全的是利用更低杠杆/不安全GHC原语并构建安全runSTArray的扩展变体。通过这种方式我们避免了复制。

runSTArray2 :: (forall s. ST s (STArray s i1 e1, STArray s i2 e2))
            -> (Array i1 e1, Array i2 e2) 
runSTArray2 st = runST $ do
  (a1, a2) <- st
  (,) <$> unsafeFreezeSTArray a1 <*> unsafeFreezeSTArray a2

我相信上面使用unsafe的东西实际上是安全的,因为我们在不安全冻结后不再使用a1,a2,因此不需要复制。

当然,上面的包装器可以推广到更多的数组。可以说,应该在库中放置更通用的版本。

最后,我们可以利用辅助功能:

test2LessSafe :: (Array Int Char, Array Int Bool)
test2LessSafe = runSTArray2 $ do
   a1 <- newArray (0,9) 'A' :: ST s (STArray s Int Char)
   a2 <- newArray (0,9) False :: ST s (STArray s Int Bool)
   writeArray a1 5 'B'
   x <- readArray a2 6
   writeArray a1 7 (if x then 'X' else 'Y')
   writeArray a2 5 True
   return (a1, a2)
© www.soinside.com 2019 - 2024. All rights reserved.