【问题标题】:Data value dependencies, updates and memoisation数据值依赖、更新和记忆
【发布时间】:2012-05-18 22:50:45
【问题描述】:

很抱歉,这个问题描述太抽象了:这是为了我的工作,出于商业机密的原因,我不能给出现实世界的问题,只是一个抽象。

我有一个应用程序接收包含键值对的消息。键来自一组定义的关键字,每个关键字都有固定的数据类型。因此,如果“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.
}

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

我还有许多需要从当前值(如果存在)计算的派生值。比如我想拥有

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 计算的值。

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

【问题讨论】:

  • 一个明显的观点——你能接受由“已知”内在数据索引的 Map 中的派生数据,而不是在记录中嵌入这两种类型的数据吗?如果您可以像这样管理数据,那么考虑何时如何重新计算会容易得多。
  • 这将强制所有派生数据为同一类型,这可能意味着将其粉碎成一个字符串。它是一个后备位置,但不是我想要的。 OTOH 也许有一些涉及“forall”的解决方案可以解决这个问题。

标签: haskell


【解决方案1】:

我假设您有一个“状态”记录,并且这些消息都涉及更新它以及设置它。所以如果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

增量计算其实也和函数响应式编程有很大关系,所以你可以看看 conal 的论文,或者玩一下 Heinrich Apfelmus 的响应式香蕉库:http://www.haskell.org/haskellwiki/Reactive-banana

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

【讨论】:

    【解决方案2】:

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

    您是否正在根据实时输入的利率等创建市场数据,例如收益率曲线?

    【讨论】:

    • 是的,这里有一个依赖图。但是我该如何构建和使用它呢?
    • 不,它不是市场数据。但问题的形式可能非常相似。大量的输入,从输入到所需结果的复杂依赖关系,以及各个输入不时变化。感觉就像箭头和镜头一样有用。但我不知道怎么做。
    【解决方案3】:

    我想要的是某种智能记忆或基于推送的重新计算,仅在参数发生变化时重新计算 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 时,会立即返回先前计算的答案。

    【讨论】:

    • 我认为他想要多次变异,所以懒惰的评估并没有削减它。
    • 随时间分配一次就是Future
    • 对不起,没有。这不是。 augustss 是正确的:惰性评估是不够的,因为我需要知道参数何时发生变化。
    猜你喜欢
    • 1970-01-01
    • 2020-04-30
    • 1970-01-01
    • 2016-12-17
    • 2014-09-17
    • 1970-01-01
    • 2013-06-16
    • 2022-05-02
    • 1970-01-01
    相关资源
    最近更新 更多