我正在阅读有关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,但如果它是非原子数据,则可以使它在调用之间进一步实例化。 –