Передача параметров в функции-мутаторы в Scala - PullRequest
0 голосов
/ 21 января 2012

Я играю в 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
}

Единственный код выше, который не компилируется, это var в def initList(), и потому данные тогдаa val Я не могу выполнить никаких мутаций в этой функции.

Позвольте мне начать с того, что я знаю, что в Scala мутаторы обычно не одобряются, поэтому я не только открыт для прямого ответа на вопросы.мой вопрос, но открыт для лучших способов сделать это полностью.Иногда в проектах есть часть данных, которая перемещается с места на место для обновления, но если вы не можете передать данные в функцию, которую нужно изменить, то что является хорошей альтернативой?

Я прочитал руководстваи google'd для этого, я полагаю, я не могу найти много об этом, потому что это обычно не делается в Scala.

Ответы [ 4 ]

4 голосов
/ 23 января 2012

Вы должны предпочесть функциональное / неизменное решение, как предложено в других ответах. Однако, когда вам действительно нужна эта возможность - для передачи значения по ссылке, вы можете использовать изменяемые ссылочные ячейки стиля ML .

Вот как вы их объявляете:

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)
4 голосов
/ 21 января 2012

Первое, что нужно понять, это то, что, хотя data - это переменная, сам список остается неизменным. Хотя вы можете назначить новый список для 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, он вернется (при условии, что данные были пустыми)

Этот метод на самом деле быстрее, чем использование цикла for, поскольку вам не нужно переворачивать список в конце. Scala может оптимизировать хвостовые рекурсивные функции, поэтому вам не нужно беспокоиться о переполнении стека или ошибках нехватки памяти.

Существует множество других способов создания и преобразования списков, я также мог бы сделать это:

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

или даже просто так:

(0 to 1000).toList
2 голосов
/ 21 января 2012

Что может быть лучше кода:

val data = 1000 to 0 by -1

Гораздо быстрее в (IMO) легче читать.

2 голосов
/ 21 января 2012

Как насчет этого:

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