5

我正在通過Odersky,Spoon和Venners編寫Scala第二版編程,這個例子讓我感覺到了一個循環,因爲它似乎違背了我認爲的功能編程的真實性,不變性。在這個例子中(以及第18章的書中較早的部分),作者聲稱,即使這些操作可能在內部改變對象的狀態,對象上的操作仍然可以是「純粹的功能」。這個例子在第442頁上, 19,是這樣的:在Scala中編程的功能隊列

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) 

是不是有可能,一個線程可以調用頭()獲得在鏡子這一點()被取消調度之前:

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) 

而當第二個線程調用尾(),而首先是不活躍,這將返回:

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

而如果第一個線程的頭()調用來完成,這將在隨後尾後退還

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

我固然沒有這樣的東西,併發編程是真正mindbending對我來說,因爲我相信這是很多人,我是偉大的:()名單上調用只是好奇我在這裏錯過了什麼。

+0

我認爲你是對的。我想你可以說,如果它被多個線程使用,那麼「功能性感覺」就會崩潰。功能程序應該是無縫併發的嗎?我不知道。 – Owen

回答

3

你是對的:當多個線程使用功能性代碼時,如果它使用內部狀態作爲其實現的一部分,它可能會呈現非功能性。您可以通過同步每個觸及可變變量的方法(即使用​​塊)來解決此問題。不幸的是,這對性能並不理想,這就是爲什麼在線程應用程序中,功能實現往往是首選。

+0

好吧,很酷,只是想確保我沒有瘋狂。有了這些定義中可能存在一點點模糊的讓步,或者至少不能完全同意它們的含義......作爲作者實際上稱之爲「純粹功能性」還是「不可變性」是否公平?它看起來在正確的條件下有效,但它肯定不是不可變的......我的意思是它很清楚地改變狀態,並且功能外觀與多個線程分離。我問,因爲我正在考慮寫作者,不想看起來像一個巨大的工具。 –

+0

@Chris K - 我認爲把它稱爲不變是公平的,但是警告線程安全也是明智之舉。計算機本質上是可變的設備;即使設計看起來不可變,幾乎總有一種方法可以使操作導致變異或其他失敗。問題在於你是否提供了_designed_來改變狀態的接口,以及它是多麼的脆弱。鑑於多線程並不是那麼不尋常,這種脆弱性令人失望,但我不確定稱它爲「不是純粹的功能」是準確的。 –