正如 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 v 的DeRef 类型。
然后您可以对生成的图形结构进行重写。
作为完全使用可观察共享的替代方案,如果您愿意让您的用户使用 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]
一旦你通过任何途径获得了图表,就有各种各样的技术来操纵它,但这些可能超出了你最初问题的范围。