【问题标题】:freezing haskell STrefs冻结haskell STrefs
【发布时间】:2012-11-28 16:34:43
【问题描述】:

我想实现一个在 Haskell 中使用的双向连接边列表数据结构。该数据结构用于管理平面中线排列的拓扑结构,并包含面、边和顶点的结构。

在我看来,这个数据结构的一个好的接口应该是 Arrangement 类型,具有类似的功能

overlay :: Arrangement -> Arrangement -> Arrangement

但通常的实现很大程度上依赖于引用(例如,每个面都有对相邻边的引用)。

在我看来,理想的工作方式类似于可变和不可变数组的工作方式:Arrangement 数据结构的内部实现为函数数据结构,但改变排列的操作“解冻”它们以在 monad 中创建新的可变实例(理想情况下使用 COW 魔法来提高效率)。

所以我的问题是:

(1) 有没有办法像数组一样冻结和解冻一个小堆? (2) 如果没有,有没有更好的方法?

【问题讨论】:

  • 我猜你对通常的打结技巧不感兴趣,而是对更底层的东西感兴趣。
  • 我有兴趣在 Haskell 中创建类似的数据结构。我理解的唯一两种方法是使用打结方法,这种方法非常优雅但修改效率低下,或者使用基于 ID 的方法和地图类型数据结构(间接),从优雅的角度来看似乎并不令人满意,但是可能效果很好。如果您有不同的想法,我想听听更多。
  • 您看过 Martin Erwig 的函数图库吗? (web.engr.oregonstate.edu/~erwig/fgl/haskell)
  • 我刚刚阅读了描述 FGL 的论文,这似乎是一种有趣的方法。我不认为我可以直接使用 FGL,但也许我可以以同样的方式实现我的图表。谢谢你!
  • 你可以只使用数组作为通用引用来实现你的结构吗?

标签: haskell data-structures reference mutable freeze-thaw


【解决方案1】:

这可能是您正在寻找的。循环应该可以正常工作。首先出现一个涉及循环的简单示例。

data List a t = Nil | Cons a t deriving (Show, Functor, Foldable, Traversable)
runTerm $ do
  x <- newVar Nil
  writeVar x (Cons 'a' (Var x)))
  return $ Var x

现在,代码。

{-# LANGUAGE
    Rank2Types
  , StandaloneDeriving
  , UndecidableInstances #-}
module Freeze
       ( Term (..)
       , Fix (..)
       , runTerm
       , freeze
       , thaw
       , Var
       , newVar
       , writeVar
       , readVar
       , modifyVar
       , modifyVar'
       ) where

import Control.Applicative
import Control.Monad.Fix
import Control.Monad.Trans.Class
import Control.Monad.Trans.Reader
import Control.Monad.ST

import Data.STRef
import Data.Traversable (Traversable, traverse)

data Term s f
  = Var {-# UNPACK #-} !(Var s f)
  | Val !(f (Term s f))

newtype Fix f = Fix { getFix :: f (Fix f) }
deriving instance Show (f (Fix f)) => Show (Fix f)

runTerm :: Traversable f => (forall s . ST s (Term s f)) -> Fix f
runTerm m = runST $ m >>= freeze

freeze :: Traversable f => Term s f -> ST s (Fix f)
freeze t = do
  xs <- newSTRef Nil
  f <- runReaderT (loop t) xs
  readSTRef xs >>= mapM_' modifyToOnly
  return f
  where
    loop (Val f) = Fix <$> traverse loop f
    loop (Var (STRef ref)) = do
      a <- lift $ readSTRef ref
      case a of
        Both _ f' ->
          return f'
        Only f -> mfix $ \ f' -> do
          lift $ writeSTRef ref $! Both f f'
          ask >>= lift . flip modifySTRef' (ref :|)
          Fix <$> traverse loop f

thaw :: Traversable f => Fix f -> ST s (Term s f)
thaw = return . loop
  where
    loop = Val . fmap loop . getFix

newtype Var s f = STRef (STRef s (Many s f))

newVar :: f (Term s f) -> ST s (Var s f)
newVar = fmap STRef . newSTRef . Only

readVar :: Var s f -> ST s (f (Term s f))
readVar (STRef ref) = fst' <$> readSTRef ref

writeVar :: Var s f -> f (Term s f) -> ST s ()
writeVar (STRef ref) a = writeSTRef ref $! Only a

modifyVar :: Var s f -> (f (Term s f) -> f (Term s f)) -> ST s ()
modifyVar (STRef ref) f = modifySTRef' ref (Only . f . fst')

modifyVar' :: Var s f -> (f (Term s f) -> f (Term s f)) -> ST s ()
modifyVar' (STRef ref) f = modifySTRef' ref (\ a -> Only $! f (fst' a))

data Many s f
  = Only (f (Term s f))
  | Both (f (Term s f)) (Fix f)

fst' :: Many s f -> f (Term s f)
fst' (Only a) = a
fst' (Both a _) = a

modifyToOnly :: STRef s (Many s f) -> ST s ()
modifyToOnly ref = do
  a <- readSTRef ref
  case a of
    Only _ -> return ()
    Both f _ -> writeSTRef ref $! Only f

data List s a = Nil | {-# UNPACK #-} !(STRef s a) :| !(List s a)

mapM_' :: Monad m => (STRef s a -> m b) -> List s a -> m ()
mapM_' _ Nil = return ()
mapM_' k (x :| xs) = k x >> mapM_' k xs

【讨论】:

    【解决方案2】:

    并不是说freezethaw 的安全版本会完整复制数组,所以不一定有那么高效。当然,相对于通过遍历结构并递归提取 MVars 等来创建结构的完整副本,制作 refs 数组的完整副本可以说是一种优化。

    另一种方法与Repa 类似——以代数方式表示对结构的操作,并编写一个run 函数来优化、融合,然后一次性执行所有操作。可以说这是一个更实用的设计。 (您甚至可以在幕后使用不安全的操作,以便按需而不是显式地进行具体化)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-24
      • 2011-10-24
      • 2015-11-17
      相关资源
      最近更新 更多