Javascript 如何编写100%纯记忆函数?

Javascript 如何编写100%纯记忆函数?,javascript,types,functional-programming,computer-science,monads,Javascript,Types,Functional Programming,Computer Science,Monads,例如,假设这是我的函数: let fib = n => { switch (n) { case 0: return 0 case 1: return 1 default: return fib(n - 1) + fib(n - 2) } } 然后我可以实现一个基本的记忆功能 let memoize = fn => { let cache = new Map // mutates! return _ => { if (!cac

例如,假设这是我的函数:

let fib = n => {
  switch (n) {
    case 0: return 0
    case 1: return 1
    default: return fib(n - 1) + fib(n - 2)
  }
}
然后我可以实现一个基本的记忆功能

let memoize = fn => {
  let cache = new Map   // mutates!
  return _ => {
    if (!cache.has(_)) {
      cache.set(_, fn(_))
    }
    return cache.get(_)
  }
}
。。。并使用它实现一个记忆化fib:

let fib2 = memoize(n => {
  switch (n) {
    case 0: return 0
    case 1: return 1
    default: return fib2(n - 1) + fib2(n - 2)
  }
})
但是,
memoize
函数在内部使用可变状态。我可以将
memoize
重新实现为monad,因此每次调用
fib
时,它都会返回一个[value,fib]元组,其中
fib
现在缓存中有一些内容,保留原始
fib
未修改

monad方法的缺点是它不是惯用的JavaScript,如果没有静态类型系统,它很难使用


有没有其他方法可以实现一个
记忆化
功能,避免变异,而不必求助于单子?

首先,即使在Haskell中,按照所有合理的标准,thunks都是“纯”的,在评估后,thunks在内部会被其结果覆盖——请阅读。这是纯度和效率之间的折衷,但是这会对用户隐藏杂质,从而使其成为一个实现细节

但你的问题的答案是肯定的。考虑一下你在一个纯命令的设置中所做的事情:动态编程。您应该考虑如何将函数构造为表查找,然后自下而上构建该表。现在,许多人应用它的是,这只是在构建一个记忆化的递归函数

您可以改变这一原则,在函数式语言中使用“自底向上的表技巧”来获得一个记忆递归函数,只需以纯粹的方式构建查找表:

fib n=fibs!!N
其中fibs=0:1:zipWith(+)fibs(尾部fibs)
翻译成懒惰的-JS:

让fibs=[0,1,Array.zipWith((a,b)=>a+b,fibs,fibs.tail)…]
设fib=(n)=>fibs[n]
在Haskell中,这在默认情况下是有效的,因为它是懒惰的。在JavaScript中,您可能可以通过使用惰性流来执行类似的操作。比较以下Scala变体:

def fibonacci(n:Int):流[Int]={
lazy val fib:Stream[Int]=0#:::fib.scan(1)(u+u)
fib.take(n)
}
scan
类似于
reduce
,但保留中间累加;
#::
是流的缺点;
惰性val
值本质上是一种记忆重击。)


关于在存在图缩减的情况下如何实现
fib
,请参见。

Ah,我一直在想Haskell是如何实现对已评估thunks结果的共享的。现在,这感觉像是欺骗。嗯,这真是一种优雅的分离。您有了纯图形,然后可以将它们编译成一个专用于计算它们的程序——可变的,但有点像“ML语言的汇编语言”。这也比“覆盖结果”更微妙。到目前为止,我还不是专家。这是有道理的,但在JS中类似于我示例中的hashmap构建方法,因为数组是可变的。节点的内置Streams类只能获取流的最新元素,因此我们必须自己跟踪已经看到的元素。听起来您的方法的要点是它仍然是可变的,但这种可变性不会暴露给程序员;因为JS没有lazy eval,所以我们需要跟踪应用层中的状态。@b Herny我想我使用的“stream”与您不同——本质上是一个懒惰的,可能是无限的列表,它可以有一个递归定义。这样的事情可以通过使用闭包的渴望语言来实现。请看我的Scala示例——虽然考虑了如何实现
lazy val
s,但您的最后一句话是正确的。现在在NPM上发布为“lazy arr”——考虑到给定的答案,如果Javascript有一个非严格的评估策略,这是可能的