防止Haskell中某些子树的可观察共享

时间:2014-10-06 03:14:06

标签: haskell ghc data-reify

我有一个EDSL,为阵列提供类似列表的组合器(mapzipWith等。)

某些组合器要求某些输入是随机访问。例如。 Gather的数据数组从另一个指定的索引处选择数据数组中的元素:

Gather (Manifest [10,11,12]) (Manifest [2,0,0,1,2]) = [12,10,10,11,12]

该语言使用data-reify包来恢复共享。问题在于,有时相同的子树包含需要提供随机访问的节点和可以按顺序计算的节点。让他们共享会破坏后续的评估者。

例如,在下面的树中,我希望[1,2,3]继续共享,但Manifests将它们包装为恢复图中的不同节点:

     [1, 2, 3]
     /       \
 Manifest Manifest
    |        |
    |     Map (+1)
    \     /
    Gather

更复杂的示例可能包含许多共享节点,所有共享节点都应该变得不同(即使可以共享Map (+1) (Manifest [1,2,3])

     [1, 2, 3]
     /       \
 Manifest Manifest
    |        |
 Map (+1)  Map (+1)
    |       /|
 Map (*2)  / |
    \     /  |
    Gather   |
        \    |
        ZipWith (+)

即使我找到了简单案例(Gather引用Manifest直接)的解决方案,它也已经涵盖了大多数用例。

欢迎任何指示!

以下是该语言的简单模型。

module NoSharing where

data AST = Manifest [Int]
         | Map (Int -> Int) AST
         | ZipWith (Int -> Int -> Int) AST AST
         | Gather AST  -- ^ Data
                  AST  -- ^ Indices

complex = ZipWith (+) gathered indexes
  where
    gathered = Gather (Map (*2) indexes) indexes
    indexes  = Map (+1) $ Manifest [1,2,3]


simple = Gather dat indexes
  where
    dat     = Manifest [1,2,3]
    indexes = Map (+1) dat

1 个答案:

答案 0 :(得分:3)

您可以这样做的一种方法是在致电data-reify之前手动取消共享。例如,此函数应该有望取消共享顶级Manifest构造函数,但保持其参数共享:

rebuildManifest :: AST -> AST
rebuildManifest (Manifest xs) = Manifest xs
rebuildManifest t = t

现在要取消共享Manifest下的任何Gather,您可以递归地执行相同的操作,并在适当时重新使用原始文件

rebuildAllManifestsUnderGather :: AST -> (AST, Bool)

rebuildAllManifestsUnderGather t@(Map f t') =
    let (newt', reuse) = rebuildAllManifestsUnderGather t'
    in if reuse then (t, True) else (Map f newt', False)

rebuildAllManifestsUnderGather t@(ZipWith f t1 t2) =
    let (newt1, reuse1) = rebuildAllManifestsUnderGather t1
        (newt2, reuse2) = rebuildAllManifestsUnderGather t2
    in if reuse1 && reuse2 then (t, True) else (ZipWith f newt1 newt2, False)

rebuildAllManifestsUnderGather t@(Gather t1 t2) =
    let (newt1, reuse1) = rebuildManifest $ rebuildAllManifestsUnderGather t1
        (newt2, reuse2) = rebuildManifest $ rebuildAllManifestsUnderGather t2
    in if reuse1 && reuse2 then (t, True) else (Gather newt1 newt2, False)

    where rebuildManifest (Manifest xs, _) = (Manifest xs, False)
          rebuildManifest (t, reuse) = (t, reuse)

rebuildAllManifestsUnderGather t@(Manifest xs) = (t, True)

但是,会被警告:无法保证可观察的共享,并且可能在两个方向上都不可靠。 GHC优化者可以在法律上“重新分享”上述取消共享Manifest的尝试。我不知道它在实践中会做些什么。

此解决方案也非常复杂,因此,考虑到其脆弱性,在调用data-reify后进行明确的取消共享传递可能更好。