Scala 编程中的函数式队列

发布于 2024-12-02 21:46:35 字数 1608 浏览 5 评论 0原文

我正在阅读 Odersky、Spoon 和 Venners 编写的《Scala 编程》第二版,这个例子让我陷入了困境,因为它似乎违背了我对函数式编程和不变性的看法。在示例中(以及本书第 18 章前面的内容),作者声称对对象的操作仍然可以是“纯函数式”,即使这些操作可能会在内部改变对象的状态。该示例位于第 442 页,第 1 章。 19,是这样的:

 class Queue[+T] private (
   private[this] var leading: List[T], 
   private[this] var trailing: List[T]
 ) {

   private def mirror() = 
     if (leading.isEmpty) {
       while (!trailing.isEmpty) {
         leading = trailing.head :: leading
         trailing = trailing.tail
       }
     }

   def head: T = { 
     mirror()
     leading.head 
   }

   def tail: Queue[T] = { 
     mirror()
     new Queue(leading.tail, trailing) 
   }

   def enqueue[U >: T](x: U) = 
     new Queue[U](leading, x :: trailing)
 }

给出的理由是,只要它的副作用对客户来说是不可见的,这样的东西就可以被认为是功能性的。我想我可以支持这一点......我的意思是严格来说这就是定义函数的内容。但不可否认(我对 JVM 内存模型的保证并不是太了解),但是这段代码中是否存在潜在的问题?

例如,如果两个线程正在该队列上运行操作,则开始时看起来像这样:

Leading: Nil
Trailing: List(1,2,3,4)

一个线程是否可能在取消调度之前调用 head() 到达mirror() 中的这一点:

private def mirror() = 
     if (leading.isEmpty) {
       while (!trailing.isEmpty) {
         leading = trailing.head :: leading
         > trailing = trailing.tail
       }
     }

在这一点上,队列看起来像这样:

Leading: List(1)
Trailing: List(1,2,3,4)

当第二个线程调用 tail() 而第一个线程不活动时,将返回此值:

Leading: Nil
Trailing: List(1,2,3,4)

而如果第一个线程的 head() 调用完成,则将在后续的 tail() 调用后返回此值那个清单:

Leading: List(2,3,4)
Trailing: Nil

我承认我不擅长这种东西和并发编程对我来说真的很令人费解,我相信对很多人来说也是如此,我只是好奇我在这里错过了什么。

I'm going through Programming In Scala 2nd Edition by Odersky, Spoon, and Venners, and this example threw me for a loop since it seemed to go against what I thought was true about functional programming and immutability. In the example (and earlier in the book in Ch. 18), the authors claim that operations on an object can still be "purely functional" even when those operations might internally mutate the state of the object. The example, which is on p.442, Ch. 19, is this:

 class Queue[+T] private (
   private[this] var leading: List[T], 
   private[this] var trailing: List[T]
 ) {

   private def mirror() = 
     if (leading.isEmpty) {
       while (!trailing.isEmpty) {
         leading = trailing.head :: leading
         trailing = trailing.tail
       }
     }

   def head: T = { 
     mirror()
     leading.head 
   }

   def tail: Queue[T] = { 
     mirror()
     new Queue(leading.tail, trailing) 
   }

   def enqueue[U >: T](x: U) = 
     new Queue[U](leading, x :: trailing)
 }

The justification given is that, so long as it side effects are not visible to clients, something like this can be considered functional. I guess I can get behind that...I mean strictly speaking that's what defines a function. But admittedly (and I'm not really too savvy about what the JVM memory model guarantees), but aren't there potential problems with that in this code?

For instance, if two threads are running operations on this queue, which looks like this to start:

Leading: Nil
Trailing: List(1,2,3,4)

Isn't it possible that one thread could call head() getting to this point in mirror() before being descheduled:

private def mirror() = 
     if (leading.isEmpty) {
       while (!trailing.isEmpty) {
         leading = trailing.head :: leading
         > trailing = trailing.tail
       }
     }

At which point the queue looks like this:

Leading: List(1)
Trailing: List(1,2,3,4)

And when a second thread calls tail() while the first is not active, this would be returned:

Leading: Nil
Trailing: List(1,2,3,4)

Whereas if the first thread's head() call were to finish, this would be returned after a subsequent tail() call on that list:

Leading: List(2,3,4)
Trailing: Nil

I'm admittedly not great at this kind of stuff and concurrent programming is really mindbending for me, as I'm sure it is for a lot of people, I'm just curious what I'm missing here.

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

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

发布评论

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

评论(1

鱼忆七猫命九 2024-12-09 21:46:35

你是对的:如果使用内部状态作为其实现的一部分,那么看起来功能性的代码在被多个线程使用时可能会变成非功能性的。您可以通过同步涉及可变变量的每个方法(即使用 synchronized 块)来解决此问题。不幸的是,这对于性能来说并不理想,这就是为什么在线程应用程序中函数实现通常是首选的原因。

You're correct: functional-looking code can be rendered non-functional when used by multiple threads if it uses internal state as part of its implementation. You can fix this by synchronizing every method that touches the mutable variables (i.e. with a synchronized block). Unfortunately, this is not ideal for performance, which is why in a threaded application functional implementations are often preferred.

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