在Haskell解开结的任何好工具?

时间:2012-01-20 17:03:15

标签: haskell circular-reference

我有一个包含几种不同类型的内部循环链接的数据结构,从而使它在cycle命令的意义上变得无限。是否有任何有趣的模块可以将这些结构折叠成使用索引的平面数据结构?

我有兴趣通过ReadShow以及Data.Serialize或类似方式序列化完整的数据结构。

构建顺序索引显然有很好的功能,但基于内存地址哈希值的索引也可以正常工作。

4 个答案:

答案 0 :(得分:18)

这实际上是不可能的;从纯代码中无法检测到列表repeat 1是循环的。实际上,在Haskell的足够深奥的实现上,它甚至可能是循环的。

在Haskell的常见实现上技术上是可行的,但是,使用一种名为 observable sharing 1 的技术,但它相当复杂,并且除非你是一位非常有经验的Haskell程序员,否则你大多数时候都不想用可观察的共享来解决你的问题。

除非你有非常特殊的要求使这个好主意,否则我建议你代表你的结构直接表示的循环图;例如,使用图表库,例如标准Data.GraphFGL

如果你想要尝试可观察的共享,我建议使用data-reify包(如Type-Safe Observable Sharing in Haskell中所述),这只需要一个简单的类型 - 类实例并具有基于IO的安全接口;它实际上基于内存地址(实际上是StableName s),正如你所建议的那样。

除非您确实需要,否则不应使用可观察共享的原因之一是它会破坏参照透明度;例如,它允许您区分repeat 11 : repeat 1,甚至let xs = 1 : xs in xslet f x = x : f x in f 1,这些取决于编译器优化!

1 可观察共享背后的基本思想是公开标准Haskell实现完成的共享,基本上可以概括为“重复值不会复制它们” ;因此let xs = 1:xs in xs是一个循环列表,因为整个xs的相同值用于其尾部,而不是每次都重新计算它,(\x -> (x,x)) expensive(其中expensive是一个长时间运行的计算产生一个大的结果)只产生两个指向expensive的指针,而不是复制thunk(这意味着,即使你强制两个字段,计算也只会进行一次,并且结果只会存储在内存中一次。)

答案 1 :(得分:5)

如@Paul Johnson的回答所述,您可以构建标签版本的结构,并使用标签作为引用结构不同部分的方法。

但是当你需要它时,你可以消除标签,留下一个优化的结构,并将所有的结打结。您保留原始版本以进行序列化,但在算法需要时使用优化版本。

以下是一些代码:

import Debug.Trace
import Data.Map

data Tree a name = Leaf a | Fork (Tree a name) (Tree a name) | Name name deriving Show

instance Monad (Tree a) where
    return x = Name x
    Leaf a >>= f = Leaf a
    Fork l r >>= f = Fork (l >>= f) (r >>= f)
    Name a >>= f = f a

tie :: Map String (Tree Int String) -> Map String (Tree Int ())
tie tree = let result = fmap (>>= find) tree
               find name = trace ("Looking up " ++ name) $ flip (findWithDefault (Leaf 0)) result name
           in  result

treerefs :: Map String (Tree Int String)
treerefs = ("root"  `insert` Fork (Name "left") (Name "right")) $
           ("left"  `insert` Fork (Leaf 1)      (Name "root"))  $
           ("right" `insert` Fork (Name "root") (Leaf 2))       $
           empty

trees = tie treerefs
root = findWithDefault (Leaf 0) "root" trees

p (Leaf a) = "Leaf " ++ show a
p (Fork _ _) = "Fork"
p (Name n) = "Name " ++ show n

l (Fork a _) = a
r (Fork _ b) = b

test = p (r (r (r (l (r (l (l (r (l (r (r (l root))))))))))))

请注意如何轻松序列化treerefs但您可以快速遍历root。我将trace调用放入,以便您可以查看名称查找的频率。它确实关闭了循环(至少在GHC中)。

A tree with cyclic references

更一般地说,您可以使用方法here来加速结点绑定,而不是Tree,而不是Monad。 (我上面的例子没有完全使用{{1}}实例,但我想与该论文建立联系。)还要与loeb比较。

答案 2 :(得分:4)

如果您的数据元素具有唯一标识符(某些论文使用术语“名称”),那么您可以构建这些标识符的表格以及它们与其他标识符相关的方式。在OO实现中(以及在ehird描述的“可观察共享”中),这是使用对象的存储器地址作为ID来完成的。函数式编程只有值,而不是具有内存地址的对象,因此在构建结构时必须自己添加唯一ID。然后,您可以通过测试ID是否属于已经看到的那些ID来检测周期。

答案 3 :(得分:1)

您可以使用简单的包装器类型来表示循环结束。

data CyclePart a = CycleMiddle a | CycleEnd a

unCyclePart :: CyclePart a -> a
unCyclePart (CycleMiddle x) = x
unCyclePart (CycleEnd x) = x

listToCycleParts :: [a] -> [CyclePart a]
listToCycleParts [] = error "empty list"
listToCycleParts [x] = [CycleEnd x]
listToCycleParts (x : xs) = CycleMiddle x : listToCylceParts xs

cycle' :: [a] -> [CyclePart a]
cylce' = cycle . listToCycleParts

uncycle :: [CyclePart a] -> [a]
uncycle (CycleMiddle x : xs) = x : uncycle xs
uncycle (CycleEnd x : _) = [x]
uncycle [] = error "purported cycle does not cycle"