首先,我是Haskell的初学者,请保持友善:)
请考虑以下示例:
{-# LANGUAGE RecordWildCards #-}
data Item = Item {itemPrice :: Float, itemQuantity :: Float} deriving (Show, Eq)
data Order = Order {orderItems :: [Item]} deriving (Show, Eq)
itemTotal :: Item -> Float
itemTotal Item{..} = itemPrice * itemQuantity
orderTotal :: Order -> Float
orderTotal = sum . map itemTotal . orderItems
是否有可能记住函数orderTotal
,因此它仅对Order
记录的“实例”执行一次,这是棘手的部分,一旦垃圾回收此命令,绑定到该实例的缓存条目就被消除了?换句话说,我不想拥有一个永远增长的缓存。
评论后编辑:
确实,在这个简单的示例中,记忆的开销可能没有回报。但是您可以想象一个场景,其中我们有一个复杂的值图(例如订单,订单项,产品,客户...),并且有很多基于这些值的派生属性(例如上面的orderTotal)。如果我们为订单总数创建一个字段,而不是使用函数来对其进行计算,则必须非常小心,以免订单不一致。
如果我们可以声明性地表达这些数据相互依赖关系(使用函数而不是字段),然后将作业委托给编译器以优化这些计算,那会不好吗?我相信,使用Haskell这样的纯净语言是可能的,尽管我缺乏这样做的知识。
[为了说明我想说的话,请看下面的代码(在Python中:]
def memoized(function):
function_name = function.__name__
def wrapped(self):
try:
result = self._cache[function_name]
except KeyError:
result = self._cache[function_name] = function(self)
return result
return property(wrapped)
class Item:
def __init__(self, price, quantity):
self._price = price
self._quantity = quantity
self._cache = {}
@property
def price(self):
return self._price
@property
def quantity(self):
return self._quantity
@memoized
def total(self):
return self.price * self.quantity
类Item
是不可变的(种类),因此我们知道每个派生属性每个实例只能计算一次。这正是memoized
函数的功能。除此之外,缓存位于实例本身(self._cache
)内部,因此将对其进行垃圾回收。
我正在寻找的是在Haskell中实现类似的功能。
记住对特定类型的值的计算的相对简单的方法是将计算的结果带入数据类型并使用智能构造函数。也就是说,将Order
数据类型写为:
data Order = Order
{ orderItems :: [Item]
, orderTotal :: Float
} deriving (Show, Eq)
请注意,orderTotal
字段将替换您的同名功能。然后,使用智能构造函数构造订单:
order :: [Item] -> Order
order itms = Order itms (sum . map itemTotal $ itms)
由于延迟计算,仅在第一次需要时才计算orderTotal
字段,此后将缓存该值。当Order
被垃圾回收时,显然orderTotal
将同时被垃圾回收。
有人会将其打包到模块中,并且仅导出智能构造函数order
而不是通常的构造函数Order
,以确保永远不会创建带有不一致orderTotal
的订单。我担心这些人。他们如何在日常生活中度过,知道自己随时可能双倍杂交?无论如何,这是真正偏执狂的可用选项。