在函数式编程语言中的自动记忆化
在函数式编程语言中的自动记忆化
我一直以为Haskell会自动进行智能记忆化。例如,这个简单的斐波那契数列的实现:
fib 0 = 0
fib 1 = 1
fib n = fib (n-2) + fib (n-1)
会因为这个原因而变得很快。现在我读到了这个,看起来我错了--Haskell似乎并没有自动记忆化。或者我理解错了?
还有其他语言会自动进行(即隐式而非显式)记忆化吗?
实现记忆化的常见方法有哪些?在我看过的所有示例实现中,它们都使用了哈希映射,但是没有限制其大小。显然,这在实践中是行不通的,因为你需要一些限制。而且,一旦达到限制,问题就变得更加复杂,因为你必须舍弃一些数据。那么问题就变得复杂了:限制可能是动态的,经常使用的函数应该有比不经常使用的函数更高的限制吗?当达到限制时,应该舍弃哪个条目?只是最新使用的那个吗?在这种情况下,你还需要对数据进行排序。你可以使用链表和哈希映射的组合来实现这一点。这是常见的方法吗?
你能否提供一些常见的实际应用实现的链接(或引用)?
谢谢,
Albert
编辑:我主要是对我描述的那个问题感兴趣,即如何实现这样的限制。任何涉及这个问题的论文参考都会非常好。
编辑:我并不试图解决特定应用中的具体问题。我正在寻找适用于(纯函数式)程序中所有函数的记忆化的通用解决方案(因此,不实现内存限制的算法不是解决方案)。当然,(可能)没有最优/最佳的解决方案。但这并不会使我的问题变得不那么有趣。
为了尝试这样的解决方案,我考虑将其作为Haskell的一种优化添加进去。我真的很想知道它的性能如何。
我想知道是否有人已经这样做了。
自动记忆化是一种在函数式编程语言中出现的问题。记忆化的原因是函数的重复调用会浪费时间和资源。解决方法包括使用共享表达式和实现缓存机制。
在Haskell中,并没有自动记忆化的功能。然而,共享表达式只会计算一次。在Paul Johnson提供的示例中,变量x作为一个thunk存储在堆上。变量y和z可以引用变量x,因为x在作用域内,并且它们引用同一个位置。一旦需要计算x,它只会被计算一次,并且只保存计算结果。因此,这并不是真正的记忆化,而是实现的结果。
还有其他一些语言可以实现自动记忆化。例如,在Python中可以使用装饰器来实现记忆化功能。此外,可以完全自己创建装饰器/实现来实现记忆化,并根据需要使用LRU等其他策略。
实现记忆化的常见方式并不存在。对于类似于斐波那契数列的模式(只有一个参数,该参数是一个数字),可以使用斐波那契示例中使用的记忆化方法。可以设计一个通用解决方案(使用哈希映射),它可以工作,但对于特定问题可能不是最优解。
记忆化涉及副作用,所以可能希望将缓存保存在State monad中。然而,通常希望将算法保持尽可能纯净,因此如果存在递归,就会产生一些问题。这是因为将递归函数的记忆化版本调用递归时,需要在State monad中运行,因此整个函数都必须在State monad中运行。这也会影响惰性计算,但可以尝试使用lazy state monad。
自动记忆化很难实现。但是,可以通过一些方法来实现。自动记忆化函数可能涉及程序转换,例如使用fix point编写代码。
一旦具备了基本的记忆化机制,可以调整查找和存储函数的缓存表来实现LRU或其他内存消耗较小的机制。可以从C++示例中获取LRU的想法。
纯记忆化没有副作用,它只是将表示参数或一组参数的thunk逐步转化为具体值。然而,Albert正在寻找更像缓存的东西,其中旧的值会被遗忘。这确实需要某种形式的副作用,尽管从调用方的角度来看,函数仍然是纯净的。
在实现自动记忆化时,需要考虑许多因素。例如,允许哪种类型的函数进行记忆化?是否记忆化作为函数的参数?如果想要仅使用Haskell进行记忆化,可以使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,需要自己管理映射。目前我所看到的情况是,要么将所有内容提升到State Monad中,要么使用unsafeperform。
在自动记忆化时,需要考虑许多因素。当您希望进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
自动记忆化是一个复杂的问题,需要考虑许多因素。当您希望进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
自动记忆化是一个复杂的问题,需要考虑许多因素。当您希望进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。哪种类型的函数可以进行记忆化?是否记忆化作为函数的参数?如果仅使用Haskell进行记忆化,您可能需要使用类似斐波那契数列的模式或者对于更复杂的类型,使用哈希映射。但是,这样就不能利用Haskell的thunk评估,并且需要手动管理映射。从目前的情况来看,这要么需要将所有内容提升到State Monad中,要么使用unsafeperform。
在进行自动记忆化时,需要考虑许多因素。
自动记忆化在函数式编程语言中的问题出现的原因是需要管理有限的内存池,并且定期清理内存以防止超出限制。这更像是一种虚拟内存页面替换算法,可以参考维基百科上关于页面替换算法的页面,了解解决这类问题的各种方法,例如“最近未使用”,“老化”,“时钟”,“第二次机会”等等。
然而,记忆化通常不是通过限制保留结果来完成的;上述算法所需的变异通常不符合Haskell的风格。但是不要因此而感到气馁。你提出了有趣的想法,可以为Haskell中记忆化可能性的探索增加有价值的补充。
有时,特定的记忆化问题很适合于有限的内存。例如,可以使用动态规划来对齐两个基因序列(参见维基百科上的“动态规划-序列对齐”),并使用二维记忆化表进行计算。但是由于给定单元格的DP解决方案仅依赖于前一行的结果,因此可以从底部开始,并丢弃距离当前行超过1的行。斐波那契数列也是一样:为了计算下一个数字,只需要前两个序列中的数字。如果你只关心第n个数字,可以丢弃之前的所有内容。
大多数记忆化是为了加速具有共享子问题的递归算法。许多这样的问题没有简单的方法来按顺序评估并丢弃不再需要的结果。此时,你只是在猜测,使用启发式算法(如使用频率)来确定谁可以获得多少有限资源的访问权限。
页面替换算法的链接很有意思。其中许多算法确实可以应用于记忆化。尽管如此,记忆化仍然有一些特殊之处。
自动记忆化在函数式编程语言中的出现原因是为了避免重复计算,提高程序的性能。在Haskell中,虽然没有自动记忆化函数的功能,但可以通过存储值的方式来实现类似的效果。例如,如果有以下代码:
x = somethingVeryLong
在同一个作用域中的其他位置有以下代码:
y = f x z = g x
那么x只会被计算一次。
有一个名为"data-memocombinators-0.4.1"的包展示了如何使用不同的键和查找表存储记忆化的值。记忆化通常在较大函数的单次调用中使用,以避免记忆化值一直存在(这可能会导致问题)。如果想要一个还能使用LRU等方式忘记旧值的记忆化器,可能需要在状态单子中实现,无法使用常规的记忆化方法让Haskell表现出这样的行为。
你提到的这个包似乎也没有考虑内存限制的问题。你是否了解任何(无论使用什么语言)不忽略这个问题的代码呢?