将参数传递给Scala中的mutator函数

时间:2012-01-21 03:36:41

标签: scala mutators

我正在斯卡拉玩耍试图掌握它,所以这段代码样本只是学术性的。

我正在尝试将一个可变列表传递给一个函数,让该函数对它执行工作,然后在函数调用后我可以使用更新列表。

var data: List[Int] = List()

// Call to function to fill a list
data = initList(data)

// Output only 0-100
data.foreach( num => if (num < 100) { println(num) })

def initList(var data: List[Int]) : List[Int] = {

    for ( i <- 0 to 1000 )
    {
        data = i :: data
    }

    data = data.reverse
    data
}

上面唯一没编译的代码是var中的def initList(),因为数据是val我不能在函数中对它执行任何突变。

首先让我说,我在Scala中知道,变异者通常不赞成,所以我不仅可以直接回答我的问题,而且可以更好地完成这个问题。有时在项目中有一条数据从一个地方到另一个地方需要更新,但是如果你不能将数据传递给一个要更改的功能那么什么是一个好的选择呢?

我已阅读教程并谷歌了解这个,我假设我找不到太多关于它的原因,因为它通常不会在Scala中这样做。

4 个答案:

答案 0 :(得分:4)

要实现的第一个重要事项是,虽然data是一个var,但列表本身仍然是不可变的。虽然您可以为data分配新列表,但您无法更改实际列表本身。这也是您无法将列表传递给函数并更改列表的原因。实际上,您的for循环正在为每次迭代创建一个新列表。

幸运的是,Scala使编写功能代码以创建列表和其他不可变数据结构变得非常容易。这是实现你想要的一般“功能”方式:

def initList(data: List[Int]) = {
  def initLoop(num: Int, buildList: List[Int]): List[Int] = num match {
    case 0 => 0 :: buildList
    case n => initLoop(n - 1, n :: buildList)
  }
  initLoop(1000, data)
}

基本上这里发生的事情是我用尾递归函数替换了for循环。每次调用时,内部函数都会通过获取当前列表并预先设置下一个数字来构建新列表,直到它变为0并返回完成的列表。由于函数从1000开始并向后返回0,因此不必反转列表。

为了让您了解其工作原理,这里是每次递归调用时内部函数参数的值(尽管让我们从3开始而不是1000):

initLoop(3, data)
initLoop(2, 3 :: data)
initLoop(1, 2 :: 3 :: data)
initLoop(0, 1 :: 2 :: 3 :: data)

所以当它最终变为0时,它返回(假设数据为空)List(0, 1, 2, 3)

此方法实际上比使用for循环更快,因为您不需要在结尾处反转列表。 Scala能够优化尾递归函数,因此您不必担心堆栈溢出或内存不足错误。

还有很多其他方法可以创建和转换列表,我也可以这样做:

val data: List[Int] = List()
(0 to 1000).foldRight(data){ (num, buildList) => num :: buildList}

甚至只是这个:

(0 to 1000).toList

答案 1 :(得分:4)

您应该更喜欢其他答案中建议的功能/不可变解决方案。但是,如果您确实需要此工具 - 通过引用传递值,则可以使用ML style mutable reference cells

这就是你声明它们的方式:

val cell: Ref[SomeClass] = Ref(value)

这是您访问其价值的方式:

!cell

这就是你改变价值的方式:

cell := !cell + 1
// OR
cell.modify(_ + 1)

一个简单的参考单元实现:

final class Ref[A] private(private var a: A) {
  def :=(newValue: A): Unit = {
    a = newValue
  }

  def unary_! : A = a

  def modify(f: A => A): Unit = {
    a = f(a)
  }
}

object Ref {
  def apply[A](value: A) = new Ref(value)
}

您可以为此添加许多有用的方法。例如递增,递减积分值。

这是使用引用单元格重写的代码(按预期工作):

def initList(data: Ref[List[Int]]): Unit = {
  for(i <- 0 to 1000)
    data := i :: !data
  data := (!data).reverse
}

val data = Ref(List.empty[Int])
initList(data)    
for(num <- !data; if num < 100)
  println(num)

答案 2 :(得分:2)

这样的事情怎么样:

def initList(inData: List[Int]) : List[Int] = {
    var data = inData  // <----The one added line
    for ( i <- 0 to 1000 )
    {
        data = i :: data
    }
    data.reverse
}

答案 3 :(得分:2)

可能是更好的代码

val data = 1000 to 0 by -1

(imo)更容易阅读更快。

相关问题