绑定到记录实例的已记忆函数的Haskell生存期
首先,我是Haskell的初学者,所以要友善:) 考虑以下示例:绑定到记录实例的已记忆函数的Haskell生存期,haskell,memoization,Haskell,Memoization,首先,我是Haskell的初学者,所以要友善:) 考虑以下示例: {-#语言记录通配符#-} 数据项=项{itemPrice::Float,itemQuantity::Float}派生(显示,等式) 数据顺序=顺序{orderItems::[Item]}派生(Show,Eq) itemTotal::Item->Float itemTotal Item{..}=itemPrice*itemQuantity 订单总计::订单->浮动 orderTotal=总和。地图项目总数。订单项 是否可以记忆函
{-#语言记录通配符#-}
数据项=项{itemPrice::Float,itemQuantity::Float}派生(显示,等式)
数据顺序=顺序{orderItems::[Item]}派生(Show,Eq)
itemTotal::Item->Float
itemTotal Item{..}=itemPrice*itemQuantity
订单总计::订单->浮动
orderTotal=总和。地图项目总数。订单项
是否可以记忆函数orderTotal
,使其在Order
记录的每个“实例”中只执行一次,并且,这是一个棘手的部分,一旦该订单被垃圾收集,绑定到此实例的缓存项将被删除?换句话说,我不想拥有一个永远增长的缓存
评论后编辑:
事实上,在这个简单的例子中,记忆的开销可能没有得到回报。但您可以想象这样一个场景:我们有一个复杂的值图(例如,订单、订单项、产品、客户等)和许多对这些值进行操作的派生属性(如上面的orderTotal)。如果我们为订单总数创建一个字段,而不是使用函数来计算它,那么我们必须非常小心,不要以不一致的订单结束
如果我们能够以声明的方式表达这些数据的相互依赖性(使用函数而不是字段),并将优化这些计算的任务委托给编译器,那岂不是一件好事?我相信,在像哈斯克尔这样的纯语言中,这是可能的,尽管我缺乏这样做的知识
为了说明我要说的内容,请看下面的代码(在Python中):
def记忆(功能):
函数名=函数名__
def包装(自我):
尝试:
结果=自身。\缓存[函数\名称]
除KeyError外:
结果=self.\u缓存[函数\u名称]=函数(self)
返回结果
返回属性(已包装)
类别项目:
定义初始(自身、价格、数量):
自身价格=价格
自身数量=数量
self.\u cache={}
@财产
def价格(自我):
返回自己的价格
@财产
def数量(自身):
返回自身数量
@回忆
def总计(自身):
返回自制价格*自制数量
类Item
是不可变的(有点),因此我们知道每个派生属性在每个实例中只能计算一次。这正是记忆功能所做的。此外,缓存位于实例本身内部(self.\u cache
),因此它将被垃圾收集
我想要的是在Haskell中实现类似的功能。对特定类型的值进行计算的一种相对简单的方法是将计算结果引入数据类型,并使用智能构造函数。也就是说,将订单
数据类型写为:
data Order = Order
{ orderItems :: [Item]
, orderTotal :: Float
} deriving (Show, Eq)
请注意,orderTotal
字段将替换同名函数。然后,使用智能构造函数构造订单:
order :: [Item] -> Order
order itms = Order itms (sum . map itemTotal $ itms)
由于延迟计算,仅在第一次需要时才计算orderTotal
字段,并在此后缓存值。当订单
被垃圾收集时,显然订单总数
将同时被垃圾收集
有些人会将其打包到模块中,只导出智能构造函数order
,而不是通常的构造函数order
,以确保永远无法创建具有不一致orderTotal
的订单。我担心这些人。他们如何度过他们的日常生活,知道他们可能会在任何时候出卖自己?无论如何,这对于真正的偏执狂来说是一个可行的选择。我怀疑跟踪实时订单
值的开销,以及它们的订单总数
值的缓存,将大于重新计算值。在证明花费在orderTotal
中的时间实际上是一个问题之前,不要担心记忆函数。即使假设记忆在这里有帮助,也无法通过仅修改orderTotal
来实现所需。这充其量只能创建一个持续整个程序的缓存,或者为每个调用创建一个“缓存”(无用)。相反,您需要修改调用orderTotal
的位置,以便在调用点创建缓存,多次使用,然后知道是时候扔掉它了。谢谢您的回复。我用更多的信息编辑了这个问题。不知道你的答案是否正确,但你对某些人的看法是提倡某些编程风格-他们绝对存在这更多的是关于推迟计算而不是记忆它。每个订单
的总额都是按需计算的,但是如果两个订单
有相同的项目,您仍然会计算两次总额。@kabuhr我非常喜欢您的解决方案,它既简单又优雅。我从来没有想到懒惰在这里会有所帮助。到目前为止,我看到的唯一缺点是,如果Order类型有很多属性,很难知道哪个参数是智能构造函数签名中的参数,因为它们没有命名。但我可以接受,我会接受你的回答。非常感谢你。