数据值依赖,更新和memoization

时间:2012-05-18 22:50:45

标签: haskell

对不起,这个问题的描述是如此抽象:它对我的工作而言,出于商业机密的原因,我不能给出现实世界的问题,只是一个抽象。

我有一个接收包含键值对的消息的应用程序。密钥来自一组定义的关键字,每个关键字都有固定的数据类型。因此,如果“Foo”是一个整数而“Bar”是一个日期,您可能会收到如下消息:

Foo: 234
Bar: 24 September 2011

消息中可能包含任何密钥子集。键的数量相当大(几十个)。但是现在让我们坚持使用Foo和Bar。

显然有一条这样的记录对应于消息:

data MyRecord {
   foo :: Maybe Integer
   bar :: Maybe UTCTime
   -- ... and so on for several dozen fields.
}

该记录使用“可能”类型,因为该字段可能尚未收到。

我还需要从当前值(如果存在)计算出许多派生值。例如,我想要

baz :: MyRecord -> Maybe String
baz r = do -- Maybe monad
   f <- foo r
   b <- bar r
   return $ show f ++ " " ++ show b

其中一些功能很慢,所以我不想不必要地重复它们。我可以为每个新消息重新计算baz,并在原始结构中备忘,但是如果一条消息使foo和bar字段保持不变,则会浪费CPU时间。相反,我可以在每次需要时重新计算baz,但如果基础参数自上次以来没有改变,则会浪费CPU时间。

我想要的是某种智能记忆或基于推送的重新计算,只有在参数改变时才重新计算baz。我可以通过注意baz仅依赖于foo和bar来手动检测到这一点,因此只能在更改这些值的消息上重新计算它,但是对于容易出错的复杂函数。

另外一个问题是这些功能中的一些可能有多种策略。例如,您可能有一个可以使用'mplus'从Foo或Bar计算的值。

有人知道现有的解决方案吗?如果没有,我该怎么办呢?

3 个答案:

答案 0 :(得分:2)

我假设你有一个“状态”记录,这些消息都涉及更新它以及设置它。因此,如果Foo12,则稍后可能为23,因此baz的输出会发生变化。如果不是这种情况,那么答案就变得非常简单了。

让我们从baz的“核心”开始 - 一个不在记录上的函数,而是你想要的值。

baz :: Int -> Int -> String

现在让我们改变它:

data Cached a b = Cached (Maybe (a,b)) (a -> b)
getCached :: Eq a => Cached a b -> a -> (b,Cached a b)
getCached c@(Cached (Just (arg,res)) f) x | x == arg = (res,c)
getCached (Cached _ f) x = let ans = f x in (ans,Cached (Just (x,ans) f)

bazC :: Cached (Int,Int) String
bazC = Cached Nothing (uncurry baz)

现在,无论何时使用普通函数,都可以使用缓存转换函数,将生成的缓存转换函数替换回记录中。这本质上是一个大小为1的手册。

对于你描述的基本情况,这应该没问题。

一个包含动态依赖关系图的更加通用且更通用的解决方案名为“增量计算”,但我看到的研究论文不仅仅是严肃的生产实现。你可以看看这些对于初学者,并按照前面的参考线索:

  1. http://www.carlssonia.org/ogi/Adaptive/
  2. http://www.andres-loeh.de/Incrementalization/paper_final.pdf
  3. 增量计算实际上也与函数式反应式编程非常相关,因此您可以查看conal的论文,或者使用Heinrich Apfelmus的反应性香蕉库:http://www.haskell.org/haskellwiki/Reactive-banana

    在命令式语言中,请查看python中的trellis:http://pypi.python.org/pypi/Trellis或lisp中的单元格:http://common-lisp.net/project/cells/

答案 1 :(得分:1)

您可以构建与您需要执行的计算相对应的有状态图。当出现新值时,将这些值推入图形并重新计算,更新图形直到达到输出。 (或者您可以将值存储在输入中并根据需要重新计算。)这是一个非常有状态的解决方案,但它可以工作。

您是否可以根据收益率等实时投入创建市场数据,如收益率曲线?

答案 2 :(得分:1)

  

我想要的是某种智能记忆或基于推送的重新计算,只有在参数发生变化时才重新计算baz。

听起来我觉得你想要一个不可变的变量,但是允许一次性变异,从“无需计算”到“计算”。嗯,你很幸运:这正是懒惰的评价给你的!因此,我提出的解决方案非常简单:只需使用字段扩展您的记录,以便计算每个要计算的内容。这是一个这样的例子,我们正在做的CPU密集型任务正在打破一些加密方案:

data Foo = Foo
    { ciphertext :: String
    , plaintext :: String
    }

-- a smart constructor for Foo's
foo c = Foo { ciphertext = c, plaintext = crack c }

这里的要点是对foo的调用有这样的费用:

  1. 如果你从不要求plaintext的结果,那就便宜了。
  2. 在第一次调用plaintext时,CPU会长时间搅拌。
  3. 在对plaintext的后续调用中,会立即返回先前计算的答案。