我正在閱讀有關Haskell背後的數學基礎 - 我瞭解瞭如何使用閉包來保存函數中的狀態。Haskell中的閉包如何工作?
我想知道Haskell是否允許關閉,以及它們如何工作,因爲它們不是純函數?
如果函數修改它的閉合狀態,它將能夠在相同的輸入上給出不同的輸出。
這在Haskell中怎麼樣?是否因爲在最初爲其賦值之後不能重新分配變量?
我正在閱讀有關Haskell背後的數學基礎 - 我瞭解瞭如何使用閉包來保存函數中的狀態。Haskell中的閉包如何工作?
我想知道Haskell是否允許關閉,以及它們如何工作,因爲它們不是純函數?
如果函數修改它的閉合狀態,它將能夠在相同的輸入上給出不同的輸出。
這在Haskell中怎麼樣?是否因爲在最初爲其賦值之後不能重新分配變量?
閉包只是'增加'了附加變量的功能,所以你可以用它們做的事比用'正常'做得更多,也就是說,不會修改狀態。
瞭解更多: Closures (in Haskell)
好的,謝謝,接受這個答案爲初學者的直覺語言 – nidoran
Haskell認爲封閉變量是值或引用? – CMCDragonkai
我想你必須首先詳細說明'參考'的含義,因爲考慮到我們處於純粹的Haskell世界,它可能會導致一些混淆。 – Bartosz
實際上,你可以在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
表示閉包的廣義消費者。
感謝您的回答,但現在大部分情況已經過去了。當我學到更多知識時,我會回到這個 – nidoran
正如其他人所說,Haskell不允許封閉中的「狀態」被改變。這可以防止你做任何可能破壞函數純度的事情。
這是否意味着如果我聲明瞭一個由函數關閉的變量,如果稍後在該函數外部修改該變量並執行該函數,該函數將忽略我的變異並仍然返回相同的輸出,因爲當我沒有改變閉合的變量時? – CMCDragonkai
Haskell不允許您稍後「修改」變量。 (或永遠)。 – MathematicalOrchid
簡單地說,你不能修改封閉狀態或任何其他:) – is7s
類似的問題:http://stackoverflow.com/questions/9419175/are-closures-a-violation-of-the-functional-編程範式/ – amindfv
@ is7s,但如果它是非原子數據,則可以使它在調用之間進一步實例化。 –