Haskell 绑定到记录实例的记忆函数的生命周期

Haskell lifetime of memoized function bound to record instance

首先,我是 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 以便它只在每个 "instance" 的 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

class 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 不一致的顺序永远不会创建。我很担心这些人。知道自己随时可能背叛自己,他们如何度过日常生活?不管怎样,对于真正的偏执狂来说,这是一个可用的选项。