我目前正在设计一种数值算法,作为其运算的一部分,它需要doubles
多次更新向量。由于该算法必须尽可能地节省空间和时间,因此,我不想对传统类型的FP代码进行编码,这种FP代码在每次对其进行操作后都会在后台创建许多版本的数据结构。我也不想创建可变的数据结构并使它们全局可用。因此,我决定使用可变数据结构,但随后选择在State
monad中进行可变操作。由于这是我第一次使用State
monad,因此我想确认是否有
该update
函数转换数据结构状态。由于破坏性更新位于该函数内,并且无法从外部获取数据结构的句柄,因此我认为此函数是纯函数且具有参照透明性。
def update(i : Int,d : Double) = State[ArraySeq[Double], Unit]{
case xs: ArraySeq[Double] => {xs(i) = d; (xs, ())}
}
该app
函数是一个玩具函数,它将消耗double
s序列并修改其状态:
def app : State[ArraySeq[Double], Unit] = for{
_ <- update(0, 3.142)
// do a heap of stuff on ArraySeq
}yield()
称呼:
app(Vector(0.0, 1.0, 2.0, 3.0, 4.0).to[ArraySeq])._1.to[Vector]
结果:
res0: Vector[Double] = Vector(3.142, 1.0, 2.0, 3.0, 4.0)
我猜您可以说您update
自己是纯洁的,就其本身而言,它仅表示某种变异,但是一旦您运行它,所有的赌注都将关闭:
scala> val xs = List(1.0, 2.0, 3.0).to[ArraySeq]
xs: scala.collection.mutable.ArraySeq[Double] = ArraySeq(1.0, 2.0, 3.0)
scala> update(0, 10).eval(xs)
res0: scalaz.Id.Id[Unit] = ()
scala> xs
res1: scala.collection.mutable.ArraySeq[Double] = ArraySeq(10.0, 2.0, 3.0)
这是一个糟糕的场景,与纯透明或参照透明相反。
State
在您的示例中并没有真正为您买任何东西-您app
以这样的方式打电话,使您拥有ArraySeq
别人无法变异的事实。您不妨硬着头皮,以一种通常的方式在您控制的范围内使用可变数据结构,即,编写app
如下:
def app(xs: Vector[Double]): Vector[Double] = {
val arr = xs.to[ArraySeq]
// Perform all your updates in the usual way
arr.toVector
}
这实际上是纯净的,并且是参照透明的,但是比State
版本更诚实。如果我看到类型的值State[Foo, Unit]
,我的假设将是这个值表示某种运算,其改变Foo
成一个新的Foo
,不变异原Foo
。这就是monad的全部状态-它提供了一种对不可变数据结构进行操作建模的好方法,并以一种看起来像变异的方式来构成它们。如果将它与实际的变异混合在一起,则可能会使使用您的代码的人感到困惑。
如果您真的想同时获得真正的突变和纯度,可以查看Scalaz's STArray
。这是解决此问题的一个非常聪明的解决方案,在Haskell这样的语言中,这是一种很有意义的方法。我自己的感觉是,这在Scala中几乎总是错误的解决方案。如果您确实需要可变数组的性能,则只需使用本地可变数组,并确保不要将其泄漏给外界。如果您不需要那种性能(大多数时候不需要),请使用State
。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句