需要闭包或lambda的示例

时间:2012-12-02 10:15:33

标签: lambda closures computer-science terminology

我花了一个小时阅读这个网站上的很多帖子,其他人关于lambdas和closures。我想我理解它们是什么,即 它们是如何工作的,但我不明白为什么它们存在。我看到的许多例子含糊地提到了他们的“权力”,但在每一种情况下,我都能想到一种更简单的方法来完成所说明的内容。也许这是因为这些例子故意过于简单(为了便于理解),或者我可能很密集。但我真正喜欢的是看到一个明确的例子,你可以通过一个闭包或lambda完成一些你没有一个不能完成的事情。也许这是一个有问题的问题,因为所有编程范例最终归结为相同的机器指令,并且可以用一种语言完成的任何操作都可以在另一种语言中完成。所以我想我真正要问的是一个用闭包做的事情的例子,比没有做的更优雅(对于我见过的任何例子似乎都不是这样)。

这就是我所说的。

What is a 'Closure'?的主要答案,这是Scheme中的一个例子:

(define (make-counter)
  (let ((count 0))
    (lambda ()
      (set! count (+ count 1))
      count)))

(define x (make-counter))

(x) returns 1

(x) returns 2

...etc...

我真的不知道Scheme,但我想我知道发生了什么。但是,这不是更直接并且完成同样的事情吗?伪代码:

function incrementCount(var counter) {
    counter++;
    return counter;
}

counter = 1;
counter = incrementCount(counter);
counter = incrementCount(counter);

这是关于lambdas的另一个:What is a lambda (function)?

给出的JavaScript示例:

var adder = function (x) {
    return function (y) {
        return x + y;
    };
};
add5 = adder(5);
add5(1) == 6

再次,为什么这样做?为什么不说:

function adder(x, y) {
    return x + y;
}
adder(5, 1);

我看到的每个例子看起来都是一种过于复杂的方式来做一些可以用基本功能轻松完成的事情。这些例子当然没有说明我能看到的任何惊人的酷“力量”。所以,请有人启发我,我一定是错过了什么。感谢。

4 个答案:

答案 0 :(得分:5)

Lambda演算实际上非常简单,而且本身并没有那么有用。然而,当你开始学习lambda演算的含义和使用功能设计模式的应用程序控制策略时,你将成为一个更好,更强大的程序员。

前提是函数也是值,这使得抽象许多概念变得非常强大。您的两个示例显示了实现闭包和currying的最简单方法。这些都是微不足道的例子。当您开始使用功能样式进行编程时,这些模式将作为抽象复杂性的非常强大的方法反复出现。

关注点分离

函数式编程很有用,当你开始使用高阶函数进行抽象时,典型的例子是 - 我想对一组对象做一些事情。

所以在命令式程序中,你使用for循环:

result = Array(length);
for(i = 0; i < length; i++)
{
   result[i] = dosomething(input[i]);
}

请注意,在代码块中,dosomething函数正好位于for循环的中间。你无法真正从实际的for-loop控制结构中分离出你对数组中每个元素做的事情。

但是,通过使用函数样式,循环的控制结构使用map - 更高阶函数 - 被抽象掉 - 您可以清楚地分离循环和函数应用这两个概念。这是方案中的等效代码。

(define result (map dosomething collection))

map负责处理遍历数组的每个元素的事实。 dosomething负责处理对数组的每个元素执行操作的事实。对此的推理变得更加清晰,并且更改代码变得更加容易。现在假设您的代码中的每个for循环都被此构造替换,它将保存多少行代码。

廉价的建设者

解决封闭和遏制的概念。对象和函数之间存在等价关系。实质上,可以使函数看起来像行为一样。 Javascript充分利用了这一事实。你可以用对象做任何事情,也可以用函数做。事实上,通过消除对象是什么和函数是什么之间的区别,你已经有效地整理了你的思想并且有一种思考问题的方法 - 也就是说,通过通过功能构建的代码。

我在这里使用clojure代码,因为这是我很满意的:

这是琐碎加法器示例的clojure代码,使用加法器示例,您调用数字为5的addn来获取向数字添加5的函数。一般用例可能是:

(defn addn [n] (fn [m] (+ m n))
(def add5 (addn 5))

(map add5 [1 2 3 4 5 6])
;; => [6 7 8 9 10 11]

假设你有一个函数download-and-save-to,它接受​​一个url和一个数据库,将url保存到数据库表并在成功时返回true,你可以使用与{{1}完全相同的抽象}}:

+

请注意,即使你做了截然不同的事情,结构也是等价的。想想你将如何使用java或c ++解决这个问题。在类定义,工厂方法,类抽象,继承......中会涉及更多代码。你必须经历更多的仪式才能达到同样的效果。

打开你的想法

由于您可以如此简洁地表达想法,函数式编程可以让您掌握许多难以用更冗长的语言表达的概念。您选择的工具将使探索某些问题更容易或更难。

我强烈推荐clojure - (http://www.4clojure.com/)和lighttable - (http://www.lighttable.com/,http://www.kickstarter.com/projects/ibdknox/light-table)开始使用。根据我的个人经验,我在一年的学习和探索clojure社区的概念类似于大约10年的学习java,c ++和python的结合。


哦,我是否提到我学习所有这些东西有多么有趣? Monads,组合,传播者,延续......当你有合适的工具来理解它们时,所有那些可怕的学术概念实际上都是可以实现的。

答案 1 :(得分:4)

这个问题很难回答,因为你已经涵盖了所有基础:你已经看过例子,描述和解释 - 你甚至意识到lambda表达式编译成一种本身没有的机器语言lambda表达式作为主要特征。

尽管如此,我将尝试提供一个示例,这个示例似乎是我个人使用lambda表达式的最常见用例之一。我使用C#,它基本上是一种命令式语言,但是将lambda表达式作为内置函数,所以我的例子将在C#中。

想象一下,你有一个需要一个值的函数。但是这个值的计算成本很高,因此要求是:​​

  • 如果该功能不需要该值,则根本不予评估。
  • 如果函数需要多次该值,则只应评估一次。

Lambda表达式使这非常简单,因为您可以使用捕获的变量来“缓存”计算值:

bool computed = false;
MyType value = null;

var getValue = () => {
    if (!computed)
    {
        value = computeValue();    // this is the expensive operation
        computed = true;
    }
    return value;
};

现在我可以将getValue传递给任何函数,无论这个函数用它做什么(即它调用它的次数),我知道computeValue()只会被调用一次。 / p>

为了在没有lambda的情况下做同样的事情,我必须创建一个等同于这个lambda的闭包的类,并让它实现某种接口,例如:

sealed class Closure : IGetMyTypeValue
{
    bool Computed = false;
    MyType Value = null;

    public Closure() { }

    public MyType GetValue()   // implements IGetMyTypeValue.GetValue()
    {
        if (!Computed)
        {
            Value = ComputeValue();    // this is the expensive operation
            Computed = true;
        }
        return Value;
    }

    private MyType ComputeValue()
    {
        // expensive code goes here
    }
}

[...]

var closure = new Closure();
// pass closure to a method that expects an IGetMyTypeValue

必须编写这个大类的缺点是:

  • 你必须为每一个这样昂贵的计算写一个新类。这是非常重复的工作;使用lambda表达式允许编译器自动执行该操作。
  • 您必须为每种类型的操作编写单独的界面。

答案 2 :(得分:2)

我不知道您是否熟悉JavaScript,但闭包和lambdas是模拟面向对象行为的基本功能。

请考虑以下代码:

var MyClass = function(buttonId) {
  var clickCounter = 0;

  var myButton = document.getElementById(buttonId);
  myButton.addEventListener(
    'onclick',
    function() {
      clickCounter += 1;
  });
};

var myButtonClickCounter = new MyClass();

在JavaScript中没有像 classes 私有成员这样的东西,但我们可以创建像它们一样的结构。 这是代码,解释了它在做什么:

// I define a class constructor that takes a string as an input parameter
var MyClass = function(buttonId) {
  // I define a private member called click
  var clickCounter = 0;

  // I get the reference to the button with the given id.
  var myButton = document.getElementById(buttonId);
  // I attach to it a function that will be triggered when you click on it.
  // The addEventListener method takes two arguments as input:
  //  - the name of the event
  //  - the function to be called when the event is triggered
  myButton.addEventListener(
    'onclick',
    function() {
      clickCounter += 1;
  });
};

// I create an instance of MyClass for a button called myButton
var myButtonClickCounter = new MyClass('myButton');

在这种情况下,传递给addEventListener方法的函数是 lambda函数。是的,你总是可以用其他方式定义它,但它只是实现细节。在这种情况下,我可以在MyClass定义中创建另一个变量而不是lambda函数,如下所示:

var MyClass = function(buttonId) {
  var clickCounter = 0;

  var clickHandler = function() {
      clickCounter += 1;
  };

  var myButton = document.getElementById(buttonId);
  myButton.addEventListener(
    'onclick',
    clickHandler
  );
};

关于JavaScript我可以告诉你什么,clickCounter在lambda函数的闭包范围内,因为它是在其链(MyClass)上面的函数中定义的,但clickHandler也会出现在该范围内,而不仅仅是clickCounter

假设这个类需要为4个不同的按钮创建一个监听器,你需要为每个按钮创建一个处理程序,每次触发一个,所有的变量都会出现在它的闭包范围内。并且有8个变量引用(4个处理程序+4个计数器)比只有4个计数器更差。

拥有此闭包的另一个好处是,clickCounter是MyClass的私有成员,因为它在其正文中被定义为变量,因此从外部看不到它。相反,clickHandler(即使在其lambda函数形式中)在内部 MyClass中定义,因此它将能够通过闭包

修改 要使clickCounter在某种程度上有用,请想象您需要一个公共方法来告诉您计数器是否已达到某个值。我们可以这样写(在定义的底部):

var MyClass = function(buttonId) {
  var clickCounter = 0;

  var myButton = document.getElementById(buttonId);
  myButton.addEventListener(
    'onclick',
    function() {
      clickCounter += 1;
  });

  // This method takes a value x as input, and returns
  // a boolean value, that tells you whether the button
  // has been clicked more than the given x.
  this.hasReached = function(x) {
    return (clickCounter >= x);
  };
};

// Somewhere else in your code...
var myButtonClickCounter = new MyClass('myButton');
...
if( myButtonClickCounter.hasReached(4) )
{
  // Do some great stuff
}

你也可以使用经典的getter和setter来简单地返回或更改私有变量的值,这取决于你需要什么。在这个特定的实现中,我不想直接公开私有变量(这是强烈建议的,因为如果类是唯一知道如何操作其成员的类,则代码更容易维护)。

注意:请记住,这只是一个显示闭包用法的​​示例,有更有效的方法在JavaScript中定义类!

答案 3 :(得分:0)

其他一些已发布的答案已使用闭包引用来创建私有变量,但我认为John Resig的高级JavaScript教程中的这个示例使其更加清晰。也许其他人也会从中受益。

来源:http://ejohn.org/apps/learn/#54

function Ninja(){
  var slices = 0;

  this.getSlices = function(){
    return slices;
  };
  this.slice = function(){
    slices++;
  };
}

var ninja = new Ninja();
ninja.slice();

console.log(ninja.getSlices()); // 1!
console.log(slices); // undefined!