Domanda

I'm playing around in Scala to try and get the hang of it, so this code sample is simply academic.

I'm trying to pass a mutable List to a function, have that function perform work on it, and then after the function call I can use the updated list.

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
}

The only code above that doesn't compile is the var in def initList(), and because data is then a val I cannot perform any mutations to it within the function.

Let me start by saying that I know in Scala, mutators are usually frowned upon, so I'm not only open to a direct answer to my question, but open to better ways to do it entirely. Occasionally in projects there is a piece of data that goes from place to place to be updated, but if you cannot pass data to a function to be changed then what is a good alternative?

I've read through tutorials and google'd for this, I'm assuming I can't find much about it because it's not usually done this way in Scala.

È stato utile?

Soluzione

The first important thing to realize is that while data is a var, the list itself is still immutable. While you can assign a new list to data, you cannot alter the actual list itself. This is also why you cannot pass a list to a function and have the list altered. So actually your for loop is creating a new list with each iteration.

Luckily, Scala makes it really easy to write functional code to create Lists and other immutable data structures. Here's the general "functional" way to do what you want:

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

Basically what's happening here is I replaced the for loop with a tail-recursive function. With each call, the inner function is building a new list by taking the current list and prepending the next number, until it gets to 0 and returns the finished list. Since the function starts at 1000 and goes backwards to 0, the list doesn't have to be reversed.

To give you an idea of how this works, here's the values of the inner function's parameters at each recursive call (though let's start at 3 instead of 1000):

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

so when it finally gets to 0, it returns (assuming data was empty) List(0, 1, 2, 3)

This method is actually faster than using the for loop since you don't need to reverse the list at the end. Scala is able to optimize tail-recursive functions so you don't have to worry about stack overflow or out-of-memory errors.

There's a ton of other ways to create and transform lists, I also could have done this:

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

or even just this:

(0 to 1000).toList

Altri suggerimenti

You should prefer the functional/immutable solution as suggested in the other answers. However when you really need this facility - to pass a value by reference, you can use ML style mutable reference cells.

This is how you declare them:

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

This is how you access their value:

!cell

And this is how you change their value:

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

A simple reference-cell implementation:

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

You can add many useful methods to this. e.g. increment, decrement for integral values.

This is your code rewritten using reference cells (works as expected):

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)

How about something like this:

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

What might be better code is

val data = 1000 to 0 by -1

Much faster in (imo) easier to read.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top