闭包如何在Haskell中运行?

时间:2012-09-15 21:18:32

标签: haskell

我正在阅读Haskell背后的数学基础 - 我已经了解了如何使用闭包来保存函数中的状态。

我想知道Haskell是否允许闭包,以及它们是如何工作的,因为它们不是纯函数?

如果函数修改了它的关闭状态,它将能够在相同的输入上给出不同的输出。

这怎么不是Haskell的问题?是不是因为在最初为变量赋值之后无法重新分配变量?

3 个答案:

答案 0 :(得分:10)

你实际上可以在Haskell中模拟闭包,但不是你想象的方式。首先,我将定义一个闭包类型:

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

这定义了一种类型,在每个“step”处取值i的值,用于计算o类型的响应。

所以,让我们定义一个“闭包”,它接受带有整数的空输入和答案,即:

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 monad:

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

可以从Control.Monad.State导入。所以我们可以将query包裹在这个State monad中:

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

...现在我们有了一种使用State monad查询任何闭包的通用方法:

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 ()

但是,我仍然不认为这是一种真正优雅的方法,因此我将以无耻的方式将我的pipes类型插入我的Server作为一种更通用,更结构化的方式来结束。写封闭和他们的消费者。 Client类型表示广义闭包,{{1}}表示闭包的广义使用者。

答案 1 :(得分:8)

闭包只是为功能添加了额外的变量,所以除了'普通'变量之外,你无法用它们做任何事情,也就是说,肯定不会修改状态。

了解更多: Closures (in Haskell)

答案 2 :(得分:1)

正如其他人所说,Haskell不允许改变闭包中的“状态”。这可以防止您做任何可能破坏功能纯度的事情。