对不起,这个问题的描述是如此抽象:它对我的工作而言,出于商业机密的原因,我不能给出现实世界的问题,只是一个抽象。
我有一个接收包含键值对的消息的应用程序。密钥来自一组定义的关键字,每个关键字都有固定的数据类型。因此,如果“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计算的值。
有人知道现有的解决方案吗?如果没有,我该怎么办呢?
答案 0 :(得分:2)
我假设你有一个“状态”记录,这些消息都涉及更新它以及设置它。因此,如果Foo
为12
,则稍后可能为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的手册。
对于你描述的基本情况,这应该没问题。
一个包含动态依赖关系图的更加通用且更通用的解决方案名为“增量计算”,但我看到的研究论文不仅仅是严肃的生产实现。你可以看看这些对于初学者,并按照前面的参考线索:
增量计算实际上也与函数式反应式编程非常相关,因此您可以查看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
的调用有这样的费用:
plaintext
的结果,那就便宜了。plaintext
时,CPU会长时间搅拌。plaintext
的后续调用中,会立即返回先前计算的答案。