加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

Scala – Monadic折叠与状态monad在恒定的空间(堆和堆栈)?

发布时间:2020-12-16 19:07:29 所属栏目:安全 来源:网络整理
导读:是否有可能在状态monad中执行一个不断的栈和堆空间的折叠?或者是不同的功能技术更适合我的问题? 接下来的部分将介绍问题和激励用例.我正在使用Scala,但Haskell的解决方案也是受欢迎的. 折叠在邦纳多填充堆 假设斯卡拉兹7.考虑一国的monadic折叠在国家monad
是否有可能在状态monad中执行一个不断的栈和堆空间的折叠?或者是不同的功能技术更适合我的问题?

接下来的部分将介绍问题和激励用例.我正在使用Scala,但Haskell的解决方案也是受欢迎的.

折叠在邦纳多填充堆

假设斯卡拉兹7.考虑一国的monadic折叠在国家monad.为了避免堆栈溢出,我们将蹦床折叠.

import scalaz._
import Scalaz._
import scalaz.std.iterable._
import Free.Trampoline

type TrampolinedState[S,B] = StateT[Trampoline,S,B] // monad type constructor

type S = Int  // state is an integer
type M[B] = TrampolinedState[S,B] // our trampolined state monad

type R = Int  // or some other monoid

val col: Iterable[R] = largeIterableofRs() // defined elsewhere

val (count,sum): (S,R) = col.foldLeftM[M,R](Monoid[R].zero){ 
    (acc: R,x: R) => StateT[Trampoline,R] {
      s: S => Trampoline.done { 
        (s + 1,Monoid[R].append(acc,x))
      }
    }
} run 0 run

// In Scalaz 7,foldLeftM is implemented in terms of foldRight,which in turn
// is a reversed.foldLeft. This pulls the whole collection into memory and kills
// the heap.  Ignore this heap overflow. We could reimplement foldLeftM to avoid
// this overflow or use a foldRightM instead.
// Our real issue is the heap used by the unexecuted State mobits.

对于一个大的集合列,这将填满堆.

我相信,在折叠期间,为集合中的每个值(x:R参数)创建一个闭包(一个状态动作),填充堆.在执行运行0之前,不能对其进行评估,从而提供初始状态.

可以避免这个O(n)堆的使用吗?

更具体地说,可以在折叠之前提供初始状态,使得状态monad可以在每个绑定期间执行,而不是嵌套闭包以供以后评估?

还是可以建造这样的折叠,以便在国家monad运行后懒洋洋地执行?这样,下一个x:R闭包将不会被创建,直到之前的一些被评估并适合于垃圾收集.

或者这种工作有更好的功能范例吗?

应用示例

但也许我正在使用错误的工具.以下是一个示例用例的演进.我在这里走错路吗?

考虑reservoir sampling,即从一个太大的集合中一次挑选一个统一的随机k个项目,以适应内存.在Scala中,这样的功能可能是

def sample[A](col: TraversableOnce[A])(k: Int): Vector[A]

如果可以像这样使用TraversableOnce类型

val tenRandomInts = (Int.Min to Int.Max) sample 10

样品完成的工作基本上是一回事:

def sample[A](col: Traversable[A])(k: Int): Vector[A] = {
    col.foldLeft(Vector()){update(k)(_: Vector[A],_: A)}
}

但是,更新是有状态的;这取决于n,已经看到的项目数量. (它也取决于RNG,但是为了简单起见,我假设这是全局和有状态的.用于处理n的技术将会简单地扩展.那么如何处理这个状态呢?

不纯的解决方案很简单,并且使用不断的堆栈和堆栈运行.

/* Impure version of update function */
def update[A](k: Int) = new Function2[Vector[A],A,Vector[A]] {
    var n = 0
    def apply(sample: Vector[A],x: A): Vector[A] = {
        n += 1
        algorithmR(k,n,acc,x)
    }
}

def algorithmR(k: Int,n: Int,acc: Vector[A],x: A): Vector[A] = {
    if (sample.size < k) {
        sample :+ x // must keep first k elements
    } else {
        val r = rand.nextInt(n) + 1 // for simplicity,rand is global/stateful
        if (r <= k)
            sample.updated(r - 1,x) // sample is 0-index
        else
            sample
    }
}

但是纯功能解决方案呢?更新必须将n作为附加参数,并返回新值以及更新的样本.我们可以将n包含在隐式状态中,折叠累加器,例如,

(col.foldLeft ((0,Vector())) (update(k)(_: (Int,Vector[A]),_: A)))._2

但这掩盖了意图;我们只打算积累样本矢量.这个问题似乎已经准备好了,对于国家monad和一个单一的左折.让我们再试一次.

我们将使用Scalaz 7与这些导入

import scalaz._
import Scalaz._
import scalaz.std.iterable_

并运行在一个Iterable [A],因为Scalaz不支持一个可变的单体折叠.

现在定义样品

// sample using State monad
def sample[A](col: Iterable[A])(k: Int): Vector[A] = {       
    type M[B] = State[Int,B]

    // foldLeftM is implemented using foldRight,which must reverse `col`,blowing
    // the heap for large `col`.  Ignore this issue for now.
    // foldLeftM could be implemented differently or we could switch to
    // foldRightM,implemented using foldLeft.
    col.foldLeftM[M,Vector[A]](Vector())(update(k)(_: Vector[A],_: A)) eval 0
}

哪里更新

// update using State monad
def update(k: Int) = {
    (acc: Vector[A],x: A) => State[Int,Vector[A]] {
        n => (n + 1,algorithmR(k,n + 1,x)) // algR same as impure solution
    }
}

不幸的是,这将堆叠在一个大集合上.

所以让我们蹦床吧.样品现在

// sample using trampolined State monad
def sample[A](col: Iterable[A])(k: Int): Vector[A] = {
    import Free.Trampoline

    type TrampolinedState[S,B]
    type M[B] = TrampolinedState[Int,B]

    // Same caveat about foldLeftM using foldRight and blowing the heap
    // applies here.  Ignore for now. This solution blows the heap anyway;
    // let's fix that issue first.
    col.foldLeftM[M,_: A)) eval 0 run
}

哪里更新

// update using trampolined State monad
def update(k: Int) = {
    (acc: Vector[A],x: A) => StateT[Trampoline,Int,Vector[A]] {
        n => Trampoline.done { (n + 1,x) }
    }
}

这修复了堆栈溢出,但仍然为非常大的集合(或非常小的堆)打击堆.一个匿名功能
集合中的值在折叠期间创建(我相信关闭每个x:A参数),在蹦床平稳运行之前消耗堆. (FWIW,状态版本也有这个问题;堆栈溢出首先是较小的集合.)

解决方法

Our real issue is the heap used by the unexecuted State mobits.

不它不是.真正的问题是,该集合不适合内存,并且foldLeftM和foldRightM强制整个集合.不纯的解决方案的一个副作用是你随时随地释放内存.在“纯粹的功能”解决方案中,你并没有在任何地方做.

您对Iterable的使用忽略了一个关键的细节:实际上是什么样的集合,它的元素如何创建以及它们如何被丢弃.所以,一定是foldLeftM在Iterable上.这可能太严格了,你们正在迫使整个收藏记忆.例如,如果它是一个Stream,那么只要你坚持到目前为止,强制的所有元素将在内存中.如果它是其他一些懒惰的Iterable,不记录其元素,那么折叠仍然是太严格.

我尝试了一个昙花一现的第一个例子,没有看到任何重大的堆压力,尽管它显然会有同样的“未执行国家的动员”.不同之处在于,EphemeralStream的元素被弱引用,其foldRight不会强制整个流.

我怀疑如果你使用Foldable.foldr,那么你不会看到有问题的行为,因为它折叠在第二个参数中是懒惰的函数.当你打电话给你时,你希望它能立即返回一个看起来像这样的暂停:

Suspend(() => head |+| tail.foldRightM(...))

当蹦床恢复第一个悬架并运行到下一个悬架时,悬架之间的所有分配将可用于由垃圾收集器释放.

尝试以下操作:

def foldM[M[_]:Monad,B](a: A,bs: Iterable[B])(f: (A,B) => M[A]): M[A] =
  if (bs.isEmpty) Monad[M].point(a)
  else Monad[M].bind(f(a,bs.head))(fax => foldM(fax,bs.tail)(f))

val MS = StateT.stateTMonadState[Int,Trampoline]
import MS._

foldM[M,R,Int](Monoid[R].zero,col) {
  (x,r) => modify(_ + 1) map (_ => Monoid[R].append(x,r))
} run 0 run

这将以恒定堆为蹦床的monad M运行,但会溢出堆栈的非蹦床monad.

但是真正的问题是,对于太大而不适合内存的数据,Iterable不是一个很好的抽象.当然,您可以编写一个必要的副作用程序,您可以在每次迭代之后明确地放弃元素或使用懒惰右折叠.这样做很好,直到你想用另一个来编写这个程序.我假设你在一个国家monad开始调查这一切的全部原因是为了获得组合.

所以,你可以做什么?以下是一些选项:

>使用减速器,单相和其组成,然后作为最后一步运行在命令性的明确释放循环(或蹦床懒惰的右折叠)中,之后不可能或预期组合.
>使用Iteratee组合和单体枚举器来饲养它们.
用Scalaz-Stream写入组成流传感器.

这些选项中的最后一个是我在一般情况下使用和推荐的选项.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读