【问题标题】:Pointers to ADTs in HaskellHaskell 中指向 ADT 的指针
【发布时间】:2014-01-06 08:47:33
【问题描述】:

我想在 Haskell 中实现术语图,这样我就可以实现一个使用共享的术语重写引擎。类似的东西

data TG f v = Var v | Op f [TG f v] | P (Ptr (TG f v))

我希望以下内容有意义:

let
    t' = Op 'f' [Var 'x', Var 'y']
    t = getPointer t'
in
    Op 'g' [P t,P t]

那么在重写的时候,我只需要重写一次。

但是,我注意到两件事:(1) 该模块被称为 Foreign.Storable,所以它应该只用于 FFI 的东西;(2) 对于列表等任何类型,都没有 Foreign.Storable 的实例;这是为什么?

【问题讨论】:

  • 按照haskell.org/haskellwiki/… 中对“可观察共享”的引用,了解几种方法的概述。
  • 你可能会发现zippers/comonads在这种情况下很方便。
  • 你不需要指针。对于可更新的引用,您可能需要STRefIORef。或者,也许您根本不需要更新。不知道你想做什么就很难知道。
  • @d8d0d65b3f7cf42:感谢您提供的链接。似乎 Gill 论文可以直接使用,而 Classen 论文需要对 Haskell 进行非保守扩展,如在 Lava 语言中所见。你知道这两个是否都有直接的 Haskell 实现吗?
  • @chunksOf50:这样的事情可能会奏效。我最终将不得不跟踪地图或某些有效数据结构中的多个替换。这是你在想的吗?会有一些效率损失,但在实践中可能可以忽略不计?

标签: pointers haskell ffi


【解决方案1】:

正如 cmets 中所指出的,如果你想在 Haskell 中定义一个普通的代数数据类型,但又想访问图形结构,你需要使用observable sharing 的一些变体。 ForeignPtr 之类的类型实际上是用于与外部代码或低级内存管理接口,并不适合这种情况。

所有可观察共享的可用技术都需要某种稍微“不安全”的代码——因为用户有责任不滥用它。问题是 Haskell 的语义并不是为了让你“看到”两个值是否是同一个指针。然而在实践中,可能发生的最坏情况是您会错过一些用户使用单个定义的情况,因此您最终会在内部数据结构中出现重复。根据您自己结构的语义,这可能只会对性能产生影响。

可观察共享通常基于pointer equality 的较低级别原语 - 即检查两个指定的 Haskell 值是否实际上存储在内存中完全相同的位置,或者更通用的 stable names,它表示内存中的位置单个 Haskell 值,可以存储在一个表中,稍后比较是否相等。

data-reify 等更高级别的库有助于向您隐藏这些详细信息。

使用可观察共享的最佳方式是允许用户编写代数类型的正常值,例如就你的例子来说:

let t = Op 'f' [Var 'x', Var 'y']
in Op 'g' [P t,P t]

然后让您的库使用任何一种可观察共享的方法,在您收到用户的值后立即将其转换为某种明确的图形结构。例如,您可以使用显式指针转换为不同的数据类型,或者用它们扩充TG 类型。显式指针只是对您自己的地图结构的某种查找,例如

data InternalTG f v = ... | Pointer Int
type TGMap f v = IntMap (InternalTG f v)

如果使用data-reify 之类的内容,那么InternalTG f v 将是TG f vDeRef 类型。

然后您可以对生成的图形结构进行重写。

作为完全使用可观察共享的替代方案,如果您愿意让您的用户使用 monad 来构建他们的值并明确选择何时使用共享(如上面包含 getPointer 所建议的那样),那么您可以简单地使用状态单子来显式地构建图形:

-- your code
data TGState f v = TGState { tgMap :: IntMap (TG f v), tgNextSymbol :: Int }

initialTGState :: TGState f v
initialTGState = TGState { tgMap = IntMap.empty, tgNextSymbol = 0 }

type TGMonad f v a = State (TGState f v) a

newtype Ptr tg = Ptr Int -- a "phantom type" just to give some type safety

getPointer :: TG f v -> TGMonad f v (Ptr (TG f v))
getPointer tg = do
   tgState <- get
   let sym = tgNextSymbol tgState
   put $
       TGState { tgMap = IntMap.insert sym tg (tgMap tgState),
                 tgNextSymbol = sym + 1 }
   return (Ptr sym)

runTGMonad :: TGMonad a -> (a, IntMap (TG f v))
runTGMonad m =
    let (v, tgState) = runState m
    (v, tgMap tgState)

-- user code

do
    let t' = Op 'f' [Var 'x', Var 'y']
    t <- getPointer t'
    return $ Op 'g' [P t,P t]

一旦你通过任何途径获得了图表,就有各种各样的技术来操纵它,但这些可能超出了你最初问题的范围。

【讨论】:

    猜你喜欢
    • 2011-08-04
    • 2019-09-10
    • 1970-01-01
    • 1970-01-01
    • 2021-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多