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

发布于 2024-12-28 07:42:13 字数 773 浏览 0 评论 0 原文

我正在尝试 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,并且因为 data 是一个 val 我无法执行任何操作函数内对其进行突变。

首先我要说的是,我知道在 Scala 中,变异器通常是不受欢迎的,所以我不仅愿意直接回答我的问题,而且愿意接受更好的方法来完全做到这一点。有时,在项目中会有一段数据需要从一个地方传送到另一个地方进行更新,但是如果您无法将数据传递给要更改的函数,那么什么是好的替代方案呢?

我已经阅读了教程并对此进行了谷歌搜索,我假设我找不到太多相关信息,因为在 Scala 中通常不会这样做。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

有深☉意 2025-01-04 07:42:13

首先要认识到的重要一点是,虽然 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 时,它返回(假设数据为空)< code>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

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
软甜啾 2025-01-04 07:42:13

您应该更喜欢其他答案中建议的功能/不可变解决方案。但是,当您确实需要此功能时 - 通过引用传递值,您可以使用 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)

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)
何处潇湘 2025-01-04 07:42:13

像这样的事情怎么样:

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

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
}
默嘫て 2025-01-04 07:42:13

更好的代码可能是

val data = 1000 to 0 by -1

速度更快(我认为)更容易阅读。

What might be better code is

val data = 1000 to 0 by -1

Much faster in (imo) easier to read.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文