2012-01-21 36 views
0

我在Scala中玩弄尝试并获取它的挂起,所以此代码示例只是学术性的。将参数传递给Scala中的增变函数

我想通过一个可变列表到一个函数,让该函数执行它的工作,然后在函数调用后,我可以使用更新列表。

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 
} 

以上这一点的唯一代码不编译是在def initList()var,并且因为数据是再val我不能在功能内执行任何突变给它。

让我开始说我知道在斯卡拉,增变器通常是不被接受的,所以我不仅可以直接回答我的问题,而且可以开放更好的方法来完成它。偶尔在项目中会有一些数据从一个地方到另一个地方进行更新,但是如果您无法将数据传递给某个函数进行更改,那么有什么好的选择?

我已经通读教程和google'd为此,我假设我找不到太多关于它,因为它通常不在Scala中这样做。

回答

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) 
} 

基本上这里发生了什么是我更换了一个尾递归函数循环。对于每次调用,内部函数都会通过获取当前列表并添加下一个数字来构建新列表,直到它变为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 
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 
} 
+0

这奏效了,我无法相信这个问题的答案是使一个不可改变的副本复印件可变一个可变的变量。有没有办法做到这一点没有副本?或者这被认为是Scala的标准做法? –

+1

那么,这实际上很少复制;将“数据”声明为一个var只是你说数据指向的引用可以改变。 'inData'默认情况下基本上是一个val,所以它在生命周期中共享与'data'相同的引用(或者至少与最初传入函数的引用相同),然后initList#数据也采用同样的引用最初的参考 - 这是三件事同时具有相同的参考! - 但它不断被for循环覆盖。 – Destin

+0

那么......还有另外一种方法吗?是的......还有其他方法,但我认为没有比这更实际的方法。它的成本非常低,它完成了你想要的。 – Destin

2

什么可能是更好的代码是

val data = 1000 to 0 by -1 

快得多(IMO)更易于阅读。

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) 
+0

这是一个很好的信息。谢谢。 –