维持状态而不分配


10

我正在学习函数式编程,无法理解如何在不使用分配的情况下实现某些特定方案。以下简单的问题几乎使我感到困惑。

编写一个程序,该程序接收有关给定数据结构更改的事件,并在此数据结构达到特定状态时发出事件。

所以我有一个我要维护的数据结构的副本

datastructure_copy::DataStructure 

当事件发生变化时,我会触发事件流:

datastructure_changes::Stream Change

我有一个将更改应用于数据结构并返回新副本的函数:

apply_change::Change -> DataStructure -> DataStructure

而且我有一个谓词,用于检查数据状态是否已达到所需状态。

is_ready::DataStructure ->Boolean

换句话说,我需要在流上工作的“ reduce”之类的东西。

我知道实现此目标的一种方法是在每次更改到达时重新计算状态,但这似乎不切实际。我在State monad玩了一点,但在我看来,这似乎是为了解决另一个问题。

那么还有另一种方法吗?

请注意,我的问题纯粹是概念性的,对Haskell并不很熟悉。


一种或另一种方式,您将永远不会在Haskell中看到“赋值”(请注意赋值和绑定之间的区别),因为“ Haskell没有“赋值”,“可变状态”或“变量”的概念,并且是“纯”的功能语言”。State Monad应该是您想要的,您只需要学习如何使用它即可。如果您愿意,我可以在今天晚些时候给出更全面的答案。
Francesco Gramano

Answers:


2

我知道实现此目标的一种方法是在每次更改到达时重新计算状态,但这似乎不切实际。

如果事件发生时应用的更改不是以一种或另一种方式进行分布的,则每次事件发生时您都必须重新计算状态,因为最终状态除了初始状态外,还包括后续更改。即使更改是分布式的,您通常也希望将状态连续转换为下一个状态,因为您希望在达到给定状态时尽快停止进程,并且由于必须计算下一个状态来确定新的是通缉状态。

在函数式编程中,状态更改通常由函数调用和/或函数参数表示。

由于您无法预测何时将计算最终状态,因此不应使用非尾递归函数。一个状态流(每个状态都基于前一个状态)可能是一个很好的选择。

因此,在您的情况下,我将通过以下代码在Scala中回答问题:

import scala.util.Random

val initState = 0.0
def nextState(state: Double, event: Boolean): Double = if(event) state + 0.3 else state - 0.1 // give a new state
def predicate(state: Double) = state >= 1

// random booleans as events
// nb: must be a function in order to force Random.nextBoolean to be called for each  element of the stream
def events(): Stream[Boolean] = Random.nextBoolean #:: events()  

val states: Stream[Double] = initState #:: states.zip(events).map({ case (s,e) => nextState(s,e)}) // a stream of all the successive states

// stop when the state is >= 1 ;
// display all the states computed before it stopped
states takeWhile(! predicate(_)) foreach println 

例如,这可以给出(我简化了输出):

0.0
0.3
0.2
0.5
0.8

val states: Stream[Double] = ... 是计算连续状态的线。

该流的第一个元素是系统的初始状态。zip将状态流与事件流合并为单个元素对流,每个元素对都是(状态,事件)。map将每对转换为作为新状态的单个值,并根据旧状态和相关事件进行计算。因此,新状态是先前计算的状态,加上“修改”状态的关联事件。

因此,基本上,您定义了一个潜在的无限状态流,每个新状态都是最后计算的状态和一个新事件的函数。由于流在Scala中(以及其他)是惰性的,因此仅按需计算,因此您不必计算无用的状态,并且可以根据需要计算任意多个状态。

如果您只对遵守谓词的第一个状态感兴趣,则将代码的最后一行替换为:

states find predicate get

哪个检索:

res7: Double = 1.1

您能否提供一些有关魔术路线的见解:val states: Stream[Double]...
Bobby Marinoff 2015年

当然。请看我的编辑。
mgoeminne 2015年

1

您说您有2个功能:

apply_change::Change -> DataStructure -> DataStructure
is_ready::DataStructure ->Boolean

如果我正确地理解了您的话,那将is_ready是非常昂贵的,因此您不想一遍又一遍地为每个变更事件都这样做。

您需要的是一个函数采用一个初始的DataStructure并将其压缩为简单状态,而一个函数采用一个压缩状态,一个Change并输出一个新的压缩状态。

假设DataStructure是一个三元组,x,y,z并且您正在等待x,y和z为质数。这样,您的压缩状态可以是x,y,z不是素数的集合。使x成为质数的Change从集合中删除x。使x不为质数的Change将x添加到集合中(如果不存在)。DataStructure准备就绪,然后集合为空。

这样的想法是,更新压缩状态比更新DataStructure和从头开始计算is_ready便宜得多。

注意:一种更好的方法可能是跟踪x,y,z中哪个被检查为素数,以及是否在哪里。对于每个更改,您都将相关字段标记为未选中。然后,在调用is_ready时,请检查并记住。如果您不每次更改后都检查is_ready,则更好,因为x可能会多次更改,并且只检查一次质数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.