2012-09-15 46 views
9

我正在閱讀有關Haskell背後的數學基礎 - 我瞭解瞭如何使用閉包來保存函數中的狀態。Haskell中的閉包如何工作?

我想知道Haskell是否允許關閉,以及它們如何工作,因爲它們不是純函數?

如果函數修改它的閉合狀態,它將能夠在相同的輸入上給出不同的輸出。

這在Haskell中怎麼樣?是否因爲在最初爲其賦值之後不能重新分配變量?

+6

簡單地說,你不能修改封閉狀態或任何其他:) – is7s

+1

類似的問題:http://stackoverflow.com/questions/9419175/are-closures-a-violation-of-the-functional-編程範式/ – amindfv

+0

@ is7s,但如果它是非原子數據,則可以使它在調用之間進一步實例化。 –

回答

8

閉包只是'增加'了附加變量的功能,所以你可以用它們做的事比用'正常'做得更多,也就是說,不會修改狀態。

瞭解更多: Closures (in Haskell)

+0

好的,謝謝,接受這個答案爲初學者的直覺語言 – nidoran

+0

Haskell認爲封閉變量是值或引用? – CMCDragonkai

+0

我想你必須首先詳細說明'參考'的含義,因爲考慮到我們處於純粹的Haskell世界,它可能會導致一些混淆。 – Bartosz

10

實際上,你可以在Haskell模擬關閉,但不是你想象的方式。首先,我將定義一個閉合類型:

data Closure i o = Respond (i -> (o, Closure i o)) 

這定義了類型,在每個「步驟」,其需要用來計算o類型的響應i類型的值。

所以,讓我們定義一個「關閉」接受空輸入,用整數答案,即:

incrementer :: Closure() Int 

這種封閉的行爲將請求之間變化。我會保持它的簡單,並使其以便它與0響應第一響應,然後增加它對於每個連續的請求響應:

incrementer = go 0 where 
    go n = Respond $ \() -> (n, go (n + 1)) 

然後,我們可以反覆查詢倒閉,這將產生一個結果和新閉合:

query :: i -> Closure i o -> (o, Closure i o) 
query i (Respond f) = f i 

注意,上述類型的第二半類似於在Haskell一個共同的模式,這是State單子:

newtype State s a = State { runState :: s -> (a, s) } 

它可以從Control.Monad.State導入。因此,我們可以換query在這個State單子:

query :: i -> State (Closure i o) o 
query i = state $ \(Respond f) -> f i 

...現在我們有一個通用的方法使用State單子查詢任何封閉:

someQuery :: State (Closure() Int) (Int, Int) 
someQuery = do 
    n1 <- query() 
    n2 <- query() 
    return (n1, n2) 

讓我們通過它我們關閉和看發生的事情:

>>> evalState someQuery incrementer 
(0, 1) 

讓我們寫一個不同的封閉返回一些任意模式:

weirdClosure :: Closure() Int 
weirdClosure = Respond (\() -> (42, Respond (\() -> (666, weirdClosure)))) 

...並對其進行測試:

>>> evalState someQuery weirdClosure 
(42, 666) 

現在,手工編寫閉包似乎很尷尬。如果我們可以使用do表示法來編寫閉包不是很好嗎?那麼,我們可以!我們只需要做出一個改變我們的閉合類型:

data Closure i o r = Done r | Respond (i -> (o, Closure i o r)) 

現在,我們可以定義一個Monad實例(從Control.Monad)爲Closure i o

instance Monad (Closure i o) where 
    return = Done 
    (Done r) >>= f = f r 
    (Respond k) >>= f = Respond $ \i -> let (o, c) = k i in (o, c >>= f) 

,我們可以寫一個對應於一個方便的功能維護一個請求:

answer :: (i -> o) -> Closure i o() 
answer f = Respond $ \i -> (f i, Done()) 

...我們可以用它來重寫我們所有的老封:

incrementer :: Closure() Int() 
incrementer = forM_ [1..] $ \n -> answer (\() -> n) 

weirdClosure :: Closure() Int r 
weirdClosure = forever $ do 
    answer (\() -> 42) 
    answer (\() -> 666) 

現在,我們只是改變我們的查詢功能:

query :: i -> StateT (Closure i o r) (Either r) o 
query i = StateT $ \x -> case x of 
    Respond f -> Right (f i) 
    Done r -> Left r 

...並用它來編寫查詢:

someQuery :: StateT (Closure() Int()) (Either()) (Int, Int) 
someQuery = do 
    n1 <- query() 
    n2 <- query() 
    return (n1, n2) 

現在測試一下吧!

>>> evalStateT someQuery incrementer 
Right (1, 2) 
>>> evalStateT someQuery weirdClosure 
Right (42, 666) 
>>> evalStateT someQuery (return()) 
Left() 

不過,我仍然不認爲這是一個真正優雅的方式,所以我要去無恥地插入我的Proxy型我pipes寫作關閉和更普遍,更結構化的方式結束自己的消費者。 Server類型表示廣義閉包,而Client表示閉包的廣義消費者。

+1

感謝您的回答,但現在大部分情況已經過去了。當我學到更多知識時,我會回到這個 – nidoran

1

正如其他人所說,Haskell不允許封閉中的「狀態」被改變。這可以防止你做任何可能破壞函數純度的事情。

+0

這是否意味着如果我聲明瞭一個由函數關閉的變量,如果稍後在該函數外部修改該變量並執行該函數,該函數將忽略我的變異並仍然返回相同的輸出,因爲當我沒有改變閉合的變量時? – CMCDragonkai

+1

Haskell不允許您稍後「修改」變量。 (或永遠)。 – MathematicalOrchid