使用闭包解决自然问题

时间:2010-05-19 13:32:16

标签: programming-languages closures

我已经阅读了很多关于闭包的文章,而且,令人尴尬的是,我仍然不明白这个概念!文章解释了如何用一些例子来创建一个闭包,但我没有看到任何关注它们的重点,因为它们在很大程度上看起来是人为的例子。我并不是说所有这些都是人为的,只是我发现它们看起来很做作,而且我知道在理解它们之后,我将能够使用它们。所以为了理解闭包,我正在研究一些真正的问题,可以使用闭包很自然地解决。

例如,解释一个人递归的一种自然方法可能是解释n!的计算。理解像使用递归计算数字的阶乘的问题是很自然的。类似地,通过读取每个元素并与所讨论的数字进行比较,在未排序的数组中找到元素几乎是明智的。此外,在不同的层面上,进行面向对象的编程也很有意义。

所以我试图找到一些可以在有或没有闭包的情况下解决的问题,但是使用闭包可以更好地思考它们并解决它们。此外,闭包有两种类型,每次调用闭包都可以创建环境变量的副本,或引用相同的变量。那么什么样的问题可以更自然地解决哪些闭包实现?

8 个答案:

答案 0 :(得分:3)

回调就是一个很好的例子。我们来看看JavaScript。

想象一下,您有一个新闻网站,标题和简短的模糊以及每个网站旁边的“阅读更多...”按钮。当用户单击该按钮时,您希望异步加载与所单击按钮对应的内容,并向用户显示所请求标题旁边的指示符,以便用户可以“看到正在其中工作的页面”。

function handleClickOnReadMore(element) {
  // the user clicked on one of our 17 "request more info" buttons.
  // we'll put a whirly gif next to the clicked one so the user knows
  // what he's waiting for...
  spinner = makeSpinnerNextTo(element);

  // now get the data from the server
  performAJAXRequest('http://example.com/',
      function(response) {
        handleResponse(response);

        // this is where the closure comes in
        // the identity of the spinner did not
        // go through the response, but the handler
        // still knows it, even seconds later,
        // because the closure remembers it.
        stopSpinner(spinner);
      }
  );
}

答案 1 :(得分:2)

当我有一个对象列表时,我个人不想写排序例程 通常你有一个sort函数和一个单独的函数来比较这两个对象 现在你可以在一个声明中做到这一点

List<Person> fList = new List<Person>();
fList.Sort((a, b) => a.Age.CompareTo(b.Age));

答案 2 :(得分:2)

好吧,过了一段时间,花了Scala,我无法想象一个代码,在没有闭包的情况下在某个列表上运行。例如:

val multiplier = (i:Int) => i * 2
val list1 = List(1, 2, 3, 4, 5) map multiplier

val divider = (i:Int) => i / 2
val list2 = List(1, 2, 3, 4, 5) map divider

val map = list1 zip list2

println(map)

输出为

List((2,0), (4,1), (6,1), (8,2), (10,2))

我不确定,如果是这个例子,你正在寻找,但我个人认为,闭包真正力量的最佳例子可以在列表上看到 - 例子:各种排序,搜索,迭代等

答案 3 :(得分:2)

我个人发现Stuart Langridge在Closures in JavaScript上的大量有用的演示文稿(pdf中的幻灯片)。它充满了很好的例子和一点幽默感。

alt text

答案 4 :(得分:1)

在C#中,函数可以通过靠近点来实现过滤的概念:

IEnumerable<Point> WithinRadius(this IEnumerable<Point> points, Point c, double r)
{
    return points.Where(p => (p - c).Length <= r);
}

lambda(闭包)捕获提供的参数并将它们绑定到稍后将执行的计算中。

答案 5 :(得分:1)

好吧,我每天都以函数组合运算符和curry运算符的形式使用Closures,两者都是通过闭包实现的:

例如,在Scheme中使用Quicksort:

(define (qsort lst cmp)
  (if (null? lst) lst
      (let* ((pivot (car lst))
             (stacks (split-by (cdr lst) (curry cmp pivot))))
        (append (qsort (cadr stacks) cmp)
                (cons pivot
                      (qsort (car stacks) cmp))))))

其中我cmp是一个二进制函数,通常用作(cmp一两),在这种情况下我讨论通过创建一个单一的谓词来分割我的堆栈,如果你愿意,我的咖喱运算符:

(define (curry f . args)
  (lambda lst (apply f (append args lst))))

(define (curryl f . args)
  (lambda lst (apply f (append lst args))))

分别是咖喱和左手。

因此,currying是闭包的一个很好的例子,另一个例子是功能组合。或者例如一个函数,它接受列表并从中生成一个谓词,该谓词测试其参数是否是该列表的成员,这也是一个闭包。

答案 6 :(得分:1)

Closure是JavaScript的强大优势之一,因为JavaScript是一种lambda语言。有关此主题的更多信息,请访问:
Trying to simplify some Javascript with closures

答案 7 :(得分:1)

好吧,说你正在生成一个菜单,比如javascript。

var menuItems = [
   { id: 1, text: 'Home' },
   { id: 2, text: 'About' },
   { id: 3, text: 'Contact' }
];

你在循环中创建它们,如下所示:

for(var i = 0; i < menuItems.length; i++) {

    var menuItem = menuItems[i];

    var a = document.createElement('a');
    a.href = '#';
    a.innerHTML = menuItem.text;

    // assign onclick listener

    myMenu.appendChild(a);

}

现在,对于onclick监听器,您可能会尝试这样的事情:

a.addEventListener('click', function() {
    alert( menuItem.id );
}, false);

但是你会发现每个链接都会提醒'3'。因为在单击时,您的onclick代码被执行,并且menuItem的值被计算到最后一个项目,因为这是在for循环的最后一次迭代时最后分配的值。 / p>

相反,您可以做的是为每个链接创建一个新的闭包,使用menuItem的值,因为它在执行时的

a.addEventListener('click', (function(item) {
    return function() {
        alert( item.id );
    }
})( menuItem ), false);

那么这里发生了什么?我们实际创建的函数接受item,然后立即调用该函数,并传递menuItem。因此,当您单击链接时,“包装”功能不会执行。我们立即执行它,并将返回的函数指定为点击处理程序。

这里发生的是,对于每次迭代,使用新值menuItem调用函数,并返回一个可以访问该值的函数,这有效地创建了一个新的闭包。

希望能够解决问题=)