封闭中的副作用,它们仍然是纯粹的功能吗?

时间:2022-03-30 22:01:13

Being relatively new to functional programming, I expend lots of energy wondering “is this the functional way to do things?” Obviously recursion vs. iteration is pretty straightforward and it’s obvious that recursion is the functional way of doing things. But take closures for instance. I’ve learned about closures using Lisp and I understand that closures are a combination of a function and an environment (sounds a lot like state and behavior). For instance:

作为函数式编程的相对较新的东西,我花费了大量的精力,想知道“这是做事的功能性方法吗?”显然递归与迭代非常简单,很明显递归是功能性的做事方式。但是以闭包为例。我已经了解了使用Lisp的闭包,我理解闭包是一个函数和一个环境的组合(听起来很像状态和行为)。例如:

(let ((x 1))
           (defun doubleX()
              (setf x (* x 2))))

Here we have a function doubleX that has been defined within the environment of the x variable. We could pass this function around to other functions and then invoke it and it will still be able to reference the x variable. The function can continue to refer to that variable, even if it is invoked outside of the environment where the variable has been defined. Many of the examples I’ve seen of closures look like this. Where setf is used to change the value of the lexical variable. This confuses me because:

这里我们有一个函数doubleX,它已在x变量的环境中定义。我们可以将此函数传递给其他函数,然后调用它,它仍然可以引用x变量。该函数可以继续引用该变量,即使它是在已定义变量的环境之外调用的。我看过闭包的许多例子都是这样的。其中setf用于更改词法变量的值。这让我很困惑,因为:

1.) I thought setf was evil. Mostly because it causes side-effects and apparently they are also evil.

1.)我认为setf是邪恶的。主要是因为它会引起副作用,显然它们也是邪恶的。

2.) Is this really “functional”? Seems like just a way of keeping global state and I thought functional languages were stateless.

2.)这真的“功能性”吗?似乎只是一种保持全球状态的方式,我认为功能语言是无国籍的。

Maybe I just don’t understand closures. Can someone help me out?

也许我只是不明白闭包。有人可以帮我吗?

3 个解决方案

#1


12  

You're right, using closures to manipulate state is not purely functional. Lisp allows you to program in a functional style, but it doesn't force you to. I actually prefer this approach because it allows me to strike a pragmatic balance between purely functional and the convenience of modifying state.

你是对的,使用闭包来操纵状态并不是纯粹的功能。 Lisp允许您以功能样式进行编程,但它不会强制您使用。我实际上更喜欢这种方法,因为它允许我在纯功能和修改状态的便利之间取得实用的平衡。

What you might try is to write something that seems functional from the outside but keeps an internal mutable state for efficiency. A great example of this is memoization where you keep a record of all the previous invocations to speed up functions like fibonacci, but since the function always returns the same output for the same input and doesn't modify any external state, it can be considered to be functional from the outside.

您可能尝试的是从外部编写看似有用的东西,但保持内部可变状态以提高效率。一个很好的例子就是memoization,你可以记录所有以前的调用来加速像fibonacci这样的函数,但是因为函数总是为同一个输入返回相同的输出并且不修改任何外部状态,所以可以考虑从外面发挥作用。

#2


8  

Closures are a poor man's objects (and vice versa), see

闭包是一个穷人的对象(反之亦然),请参阅

When to use closure?

什么时候关闭?

and my answer therein. So if you intend to use side-effects to manage state in your non-OO application, closures-over-mutable-state are indeed an easy way to do this. Immutable alternatives are "less evil", but 99.9% of languages offer mutable state and they can't all be wrong. :) Mutable state is valuable when used judiciously, but it can be especially error-prone when used with closures & capture, as seen here

我的回答。因此,如果您打算在非OO应用程序中使用副作用来管理状态,那么关闭over-mutable-state确实是一种简单的方法。不可改变的替代品“不那么邪恶”,但99.9%的语言提供可变状态,并且它们不可能都是错误的。 :)明智地使用可变状态是有价值的,但是当与闭包和捕获一起使用时,它可能特别容易出错,如此处所示

On lambdas, capture, and mutability

关于lambdas,捕获和可变性

In any case, I think the reason you see "so many examples like this" is that one of the most common ways to explain the behavior of closures is to show a tiny example like this where a closure captures a mutable and thus becomes a mini-stateful-object that encapsulates some mutable state. It's a great example to help ensure you understand the lifetime and side-effect implications of the construct, but it's not an endorsement to go and use this construct all over the place.

在任何情况下,我认为你看到“这么多这样的例子”的原因是解释闭包行为的最常见方法之一是显示一个像这样的小例子,其中一个闭包捕获一个可变的,从而变成一个迷你-stateful-object,封装了一些可变状态。这是一个很好的例子,可以帮助确保您了解构造的生命周期和副作用,但是并不认可在整个地方使用这个构造。

Most of the time with closures you'll just close over values or immutable state and 'not notice' that you're doing it.

大部分时间关闭,你只需关闭值或不可变状态,并且“不注意”你正在做它。

#3


2  

Common Lisp and Scheme are not purely functional. Clojure is mostly functional, but still not purely. Haskell is the only language I know that is purely functional, I can't even mention the name of another one.

Common Lisp和Scheme不是纯粹的功能。 Clojure主要是功能性的,但仍然不是纯粹的。 Haskell是我所知道的唯一纯粹功能的语言,我甚至不能提到另一个语言的名称。

The truth is that working in a purely functional environment is very hard (go, learn Haskell and try to program something on it). So all these functional programming languages really what they do is allow functional programming, but not enforce it. Functional programming is very powerful, so use it whenever you can and when you can't don't.

事实是,在纯粹的功能环境中工作非常困难(去学习Haskell并尝试在其上编程)。因此,所有这些函数式编程语言真正实现的是允许函数式编程,但不强制执行。功能编程非常强大,因此请尽可能地使用它,何时不能使用。

Something important to know with the age that's coming is that anything that's functional is paralelizable, so it makes sense to avoid having side effects, or having in a smallest possible subset of your program as possible.

对于即将到来的年龄而言,重要的是要知道任何具有功能性的东西都是可以兼容的,因此避免产生副作用或尽可能使用最小的程序子集是有意义的。

#1


12  

You're right, using closures to manipulate state is not purely functional. Lisp allows you to program in a functional style, but it doesn't force you to. I actually prefer this approach because it allows me to strike a pragmatic balance between purely functional and the convenience of modifying state.

你是对的,使用闭包来操纵状态并不是纯粹的功能。 Lisp允许您以功能样式进行编程,但它不会强制您使用。我实际上更喜欢这种方法,因为它允许我在纯功能和修改状态的便利之间取得实用的平衡。

What you might try is to write something that seems functional from the outside but keeps an internal mutable state for efficiency. A great example of this is memoization where you keep a record of all the previous invocations to speed up functions like fibonacci, but since the function always returns the same output for the same input and doesn't modify any external state, it can be considered to be functional from the outside.

您可能尝试的是从外部编写看似有用的东西,但保持内部可变状态以提高效率。一个很好的例子就是memoization,你可以记录所有以前的调用来加速像fibonacci这样的函数,但是因为函数总是为同一个输入返回相同的输出并且不修改任何外部状态,所以可以考虑从外面发挥作用。

#2


8  

Closures are a poor man's objects (and vice versa), see

闭包是一个穷人的对象(反之亦然),请参阅

When to use closure?

什么时候关闭?

and my answer therein. So if you intend to use side-effects to manage state in your non-OO application, closures-over-mutable-state are indeed an easy way to do this. Immutable alternatives are "less evil", but 99.9% of languages offer mutable state and they can't all be wrong. :) Mutable state is valuable when used judiciously, but it can be especially error-prone when used with closures & capture, as seen here

我的回答。因此,如果您打算在非OO应用程序中使用副作用来管理状态,那么关闭over-mutable-state确实是一种简单的方法。不可改变的替代品“不那么邪恶”,但99.9%的语言提供可变状态,并且它们不可能都是错误的。 :)明智地使用可变状态是有价值的,但是当与闭包和捕获一起使用时,它可能特别容易出错,如此处所示

On lambdas, capture, and mutability

关于lambdas,捕获和可变性

In any case, I think the reason you see "so many examples like this" is that one of the most common ways to explain the behavior of closures is to show a tiny example like this where a closure captures a mutable and thus becomes a mini-stateful-object that encapsulates some mutable state. It's a great example to help ensure you understand the lifetime and side-effect implications of the construct, but it's not an endorsement to go and use this construct all over the place.

在任何情况下,我认为你看到“这么多这样的例子”的原因是解释闭包行为的最常见方法之一是显示一个像这样的小例子,其中一个闭包捕获一个可变的,从而变成一个迷你-stateful-object,封装了一些可变状态。这是一个很好的例子,可以帮助确保您了解构造的生命周期和副作用,但是并不认可在整个地方使用这个构造。

Most of the time with closures you'll just close over values or immutable state and 'not notice' that you're doing it.

大部分时间关闭,你只需关闭值或不可变状态,并且“不注意”你正在做它。

#3


2  

Common Lisp and Scheme are not purely functional. Clojure is mostly functional, but still not purely. Haskell is the only language I know that is purely functional, I can't even mention the name of another one.

Common Lisp和Scheme不是纯粹的功能。 Clojure主要是功能性的,但仍然不是纯粹的。 Haskell是我所知道的唯一纯粹功能的语言,我甚至不能提到另一个语言的名称。

The truth is that working in a purely functional environment is very hard (go, learn Haskell and try to program something on it). So all these functional programming languages really what they do is allow functional programming, but not enforce it. Functional programming is very powerful, so use it whenever you can and when you can't don't.

事实是,在纯粹的功能环境中工作非常困难(去学习Haskell并尝试在其上编程)。因此,所有这些函数式编程语言真正实现的是允许函数式编程,但不强制执行。功能编程非常强大,因此请尽可能地使用它,何时不能使用。

Something important to know with the age that's coming is that anything that's functional is paralelizable, so it makes sense to avoid having side effects, or having in a smallest possible subset of your program as possible.

对于即将到来的年龄而言,重要的是要知道任何具有功能性的东西都是可以兼容的,因此避免产生副作用或尽可能使用最小的程序子集是有意义的。