在某处有基于对象身份的,线程安全的memoization库吗?

cir*_*uin 13 haskell memoization

我知道memoization似乎是关于堆栈溢出的haskell标记的一个常年话题,但我认为这个问题以前没有被问过.

我知道几个不同的'现成'Haskell的memoization库:

  • memo-combinators和memotrie包,它们利用一个涉及懒惰无限数据结构的漂亮技巧,以纯粹的功能方式实现memoization.(据我所知,前者稍微灵活一些,而后者在简单的情况下更容易使用:请参阅此SO答案进行讨论.)
  • uglymemo包,它在内部使用unsafePerformIO,但仍然提供了一个引用透明的接口.在内部使用unsafePerformIO 可以获得比前两个包更好的性能.(现成的,它的实现使用基于比较的搜索数据结构,而不是可能稍微更有效的散列函数;但我认为如果你找到并替换Cmpfor HashableData.Mapfor Data.HashMap和添加appropraite imports,你得到一个基于哈希的版.)

但是,我不知道任何基于对象标识而不是对象值查找答案的库.这可能很重要,因为有时用作备忘录表的键的对象类型(即,作为被记忆的函数的输入)可能很大---如此之大以至于完全检查对象以确定是否之前看到它本身就是一个缓慢的操作.慢,而且也没有必要的,如果你将一次又一次地施加memoized函数被存储在给定的"位置在存储器"的对象1.(例如,如果我们正在记忆一个函数,这个函数是在一些具有大量结构共享的大型数据结构上递归调用的.)如果我们之前已经在那个确切的对象上计算了memoized函数,我们可以已经知道了答案,即使没有看对象本身!

实现这样的memoization库涉及一些微妙的问题,并且正确地执行它需要语言的几个特殊支持.幸运的是,GHC提供的特殊功能,我们需要的,是有的佩顿-琼斯,马洛和Elliott基本上担心这些问题多数为你解释如何建立一个坚实的实现.他们没有提供所有细节,但他们接近了.

我可以看到哪一个可能应该担心的细节,但是他们担心的是线程安全---他们的代码显然根本不是线程安全的.

我的问题是:有没有人知道一个打包的库,它在Peyton-Jones,Marlow和Elliott论文中讨论了那种记忆,填写了所有的细节(最好还填写了适当的线程安全性)?

如果做不到这一点,我想我将不得不自己编写代码:是否有人对其他细微之处(除了线程安全和论文中讨论的内容)有任何想法,这样的库的实现者会记住哪些?


UPDATE

按照@ luqui的建议,下面是关于我面临的确切问题的更多数据.我们假设有一种类型:

data Node = Node [Node] [Annotation]
Run Code Online (Sandbox Code Playgroud)

这种类型可以用来表示内存中一种简单的有根DAG,其中Nodes是DAG节点,root只是一个区分Node,每个节点都注明了一些Annotations,我认为其内部结构不需要关注我们(但是如果它很重要,那就问一下,我会更具体.)如果以这种方式使用,请注意Node内存中的s 之间可能存在显着的结构共享- 可能会有指数级的路径从根到a节点比节点本身.我从一个外部库中获得了这种形式的数据结构,我必须与它接口; 我无法更改数据类型.

我有一个功能

myTransform : Node -> Node
Run Code Online (Sandbox Code Playgroud)

细节不需要我们关注(或者至少我是这么认为的;但如果需要的话,我可以更具体).它将节点映射到节点,检查它给出的节点的注释,以及它的直接子节点的注释,以提出Node具有相同子节点但可能具有不同注释的新节点.我想写一个函数

recursiveTransform : Node -> Node
Run Code Online (Sandbox Code Playgroud)

其输出与数据结构"看起来一样",就像你做的那样:

recursiveTransform Node originalChildren annotations = 
   myTransform Node recursivelyTransformedChildren annotations
   where
     recursivelyTransformedChildren = map recursiveTransform originalChildren    
Run Code Online (Sandbox Code Playgroud)

除了它以明显的方式使用结构共享,以便它不返回指数数据结构,而是返回与其输入大小相同的数据结构.

我很欣赏,如果说,Nodes在我得到它们之前已经编号,或者我可以改变a的定义,这将更容易Node.我不能(轻松)做这些事情.


我也对存在实现我提到的功能的库的一般问题感兴趣,这个功能完全独立于我现在面临的具体问题:我觉得我必须在几次这个问题上解决这个问题,一劳永逸地杀死龙会很好.事实上,SPJ等人认为值得为GHC添加一个而不是三个特征来支持这种形式的库的存在,这表明该特征真正有用,并且无法在所有情况下解决.(但我仍然非常有兴趣听到有助于这个特殊情况的解决方法:长期问题并不像我现在遇到的问题那么紧急:-))

1从技术上讲,我并不是指内存中的位置,因为垃圾收集器有时会移动一些物体 - 我真正的意思是'对象识别'.但我们可以认为这与我们在记忆中的位置的直观概念大致相同.

ham*_*mar 6

如果您希望基于对象的身份,而不是平等memoize的,你可以使用内置的语言现有的懒惰机制.

例如,如果您有这样的数据结构

data Foo = Foo { ... }
expensive :: Foo -> Bar
Run Code Online (Sandbox Code Playgroud)

然后你可以添加要记忆的值作为额外的字段,并让懒惰为你处理其余的事情.

data Foo = Foo { ..., memo :: Bar }
Run Code Online (Sandbox Code Playgroud)

为了更容易使用,添加一个智能构造函数来打结.

makeFoo ... = let foo = Foo { ..., memo = expensive foo } in foo
Run Code Online (Sandbox Code Playgroud)

虽然这比使用库稍微不那么优雅,并且需要修改数据类型才真正有用,但这是一种非常简单的技术,所有线程安全问题都已经为您解决了.

  • 您可以将memoized值存储在包装器类型中,但是您必须提升现有计算(至少需要使用memoized值的计算)来处理包装类型,即`data MemoFoo = MemoFoo Foo Bar`,用`makeFoo ... =让foo = Foo {...}在MemoFoo foo(昂贵的foo)`. (2认同)