从λ演算到函数式编程聊闭包(1):闭包概念在Java/PHP/JS中形式
什么是闭包
如果让谷哥找一下“闭包”这个词,会发现网上关于闭包的文章已经不计其数
维基百科上对闭包的解释就很经典:在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。 Peter J. Landin 在1964年将术语闭包定义为一种包含环境成分和控制成分的实体。闭包是可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。“闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。
百度百科:
闭包概念:
闭包就是有权访问另一个函数作用域中变量的函数.
分析这句话:
1.闭包是定义在函数中的函数.
2.闭包能访问包含函数的变量.
3.即使包含函数执行完了, 被闭包引用的变量也得不到释放.
在 Scala、Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby、 Python、Go、Lua、objective c、swift 以及Java(Java8及以上)等语言中都能找到对闭包不同程度的支持。
抽象代数中的闭包
在离散数学(具体的说是抽象代数)里,如果对一个集合中的每个元素执行某个运算操作,得到的结果还是这个集合的元素,那么就说该集合在这个运算操作下构成闭包。例如,整数集合在减法运算下构成闭包;但是自然数在减法运算下不构成闭包。
封闭的定义
闭包性质
一个集合满足闭包性质当且仅当这个集合在某个运算或某些运算的搜集下是封闭的,其中“某些运算的搜集下封闭”是指这个集合单独闭合在每个运算之下。
如此讲下去呢,哎,肯你说。我勒去…好高大上啊,完全看不懂!!!…………好吧。!!至此,打住……关于
函数式编程中的闭包
在这一章节开始之前,我需要再和大家明确一个比较纠结的事实,就是在函数式编程领域中当说到“闭包”时,也有可能是指数学领域中闭包的概念,这是因为函数式编程在基础理论与抽象代数有一定亲缘性,所以当在函数式语言著作中讨论“闭包”时,有可能是在抽象数学的上下文中讨论的。然而,在表述上可能会有微妙变化。在函数式语言领域对于数学闭包常用的表述是“如果一个运算的结果仍然能被此运算作用,则这个运算是封闭的”,要注意这只不过是上文提到的“闭包”概念的另一种等价表述而已,如果我们将这个运算的所有结果看做一个集合,那么就可以等价表述说这个运算在这个集合上是封闭的。
而我下面所要阐述的闭包是一种截然不同的概念。所以,当在函数式语言的著作中看到“闭包”时,需要根据上下文环境小心区分其表述哪种概念。
Lambda演算与自由变量
函数式编程语言的基础是lambda演算,这是一套用于研究函数定义、应用和递归的形式系统,由数学家丘奇在20世纪30年代引入。如果您不太熟悉lambda演算,那么维基百科相关页面是很好的快速入门资料,请原谅我不会完整描述lambda演算(因为已经有很多可以参考的资料)。
简单来说lambda演算将计算过程看过一系列的函数代换,例如,下面是加运算的lambda函数(假设+运算已经定义):λx.λx+y
lambda演算就是反复将函数应用于实际值,并用实际值代替参数,最终得出结果。例如计算7+2:(λx.λx+y)7 2=>(λy.7+y)2=>7+2=>9
首先用第一个参数(7)代替最外层函数的参数(x),然后用第二个参数(2)代替第二层函数的参数(y),最终得到计算结果。
鉴于如果下面大量使用lambda演算描述问题大家可能会崩溃(我也会崩溃),我将改用函数式语言scheme(Lisp的一个方言)来进行问题描述。注意其实scheme在本质上与lambda演算是等价的,只是看起来更好懂,例如不需要遵循lambda演算一个变态的规定:每个函数只允许有一个参数(虽然任何多参数函数式程序都可以通过Currying过程化归为等价的lambda演算)。
下面是用scheme程序对上述lambda演算的等价表示:(define (f x y) (+ x y))
可以这样计算7+2:(f 7 2);Value: 9
下面看一个稍微复杂点的例子:(define (f x) (lambda (y) (+ x y)))
这里定义了函数f,接受一个参数x,特别要注意它的返回值:不是一个值而是一个匿名函数。如果我们把这个函数单独拿出来:(lambda (y) (+ x y))
可以看到,这个匿名函数接收一个参数y,但是却没有参数x!也就是说,如果脱离上下文执行这个函数,则x处于未指定状态,我们说对于这个函数,y是绑定的,而x是自由的。
一般地:x是一个函数的函数体中的变量,如果x被这个函数的参数指定,则x是绑定于这个函数的,否则说x对于此函数是自由的。
下面可以看到,变量的绑定和自由概念是理解闭包本质的一把钥匙。关于这方面的内容,推荐阅读:《闭包漫谈(从抽象代数及函数式编程角度)》
程序语言的闭包性质
继续上面的scheme程序,我们已经定义了函数f:(define (f x) (lambda (y) (+ x y)))
如果我们运行下面程序:(f 7);Value 13: #[compound-procedure 13]
可以看到,f返回了一个过程(匿名函数),按照函数演算规则,这个函数应该是:(lambda (y) (+ 7 y))
那么下面的运算就很直观了:((f 7) 2);Value: 9
注意这里有一个非常重要的地方(也是闭包性质的关键),那就是这个运算执行了两个函数:f和匿名函数。而f的作用域为(f 7),这就是说,其实在(f 7)之后,f这个函数就结束了,而x(这里被赋值为7)是f的私有变量(绑定于f),那么程序设计语言的设计者就有两种选择:
第一,在函数超出其作用域后立即销毁其绑定变量,如果是这样的话,((f 7) 2) 是无法得出结果的,因为在外层的f运算结束后,存放数值“7”的变量就被释放了,所以匿名函数无法得到其自由变量x的值;
第二,如果一个函数返回另一个函数,而被返回函数又需要外层函数的变量时,不会立即释放这个变量,而是允许被返回的函数引用这些变量。支持这种机制的语言称为支持闭包机制,而这个内部函数连同其自由变量就形成了一个闭包。
显然scheme的设计者做了第二种选择。
上面的这段话不太好理解,但是请务必多读几遍,因为,这就是闭包的全部。
在程序语言中,闭包就是一种语法糖,它以很自然的形式,把我们的目的和我们的目的所涉及的资源全给自动打包在一起,以某种自然、尽量不让人误解的方式让人来使用。至于其具体实现,我个人意见,在不影响使用的情况下,不求甚解即可。在很多情况下,需要在一段代码里去访问外部的局部变量,不提供这种语法糖,需要写非常多的代码,有了闭包这个语法糖,就不用写这么多代码,自然而然的就用了。
java闭包,推荐阅读《深入理解Java闭包概念》
简单理解:闭包能够将一个方法作为一个变量去存储,这个方法有能力去访问所在类的自由变量。
Java中闭包实现,关键点:
如何用变量去存储方法?
java中能够保存方法的变量指的就是普通的对象
如何让这个普通对象能够访问所在类的自由变量?
纯天然的解决办法是:内部类。内部类能够访问外部类的所有属性及方法。
隐藏具体实现是内部类的作用之一,如何保证隐藏具体实现的同时还能将闭包传递到外部使用?
让内部类实现通用接口,然后将内部类对象向上转型为接口类型。
上述解决办法就是Java最常用的闭包实现办法(内部类+接口)
public class Milk {
public final static String name = "纯牛奶";//名称
private static int num = 16;//数量
public Milk()
{
System.out.println(name+":16/每箱");
}
/**
* 闭包
* @return 返回一个喝牛奶的动作
*/
public Active HaveMeals()
{
return new Active()
{
public void drink()
{
if(num == 0)
{
System.out.println("木有了,都被你丫喝完了.");
return;
}
num--;
System.out.println("喝掉一瓶牛奶");
}
};
}
/**
* 获取剩余数量
*/
public void currentNum()
{
System.out.println(name+"剩余:"+num);
}
}
/**
* 通用接口
*/
interface Active
{
void drink();
}
php闭包
php中,闭包(Closure)又叫做匿名函数,也就是没有定义名字的函数。
//例一
//在函数里定义一个匿名函数,并且调用它
function printStr() {
// 定义一个闭包,并把它赋给变量 $func
$func = function( $str ) {
echo $str;
};
$func( 'some string' );
}
printStr(); //例二
//在函数中把匿名函数返回,并且调用它
function getPrintStrFunc() {
$func = function( $str ) {
echo $str;
};
return $func;
}
$printStrFunc = getPrintStrFunc();
$printStrFunc( 'some string' ); //例三
//把匿名函数当做参数传递,并且调用它
function callFunc( $func ) {
$func( 'some string' );
}
$printStrFunc = function( $str ) {
echo $str;
};
callFunc( $printStrFunc );
//也可以直接将匿名函数进行传递。如果你了解js,这种写法可能会很熟悉
callFunc( function( $str ) {
echo $str;
} );
javascript闭包
function f(x){
return function(y) {
return x + y;
};
}
var lam = f(7);
console.log(lam(2));
对于java,闭包用的真的不多,在JS,闭包是必考题。下篇讲解JS闭包:《从抽象代数漫游函数式编程(2):话说JavaScript闭包》
参考文章:
闭包漫谈(从抽象代数及函数式编程角度)http://www.codinglabs.org/html/closure-perspective-of-abstract-mathematic-and-functional-language.html
Brendan Eich的自述 http://brendaneich.com/2008/04/popularity/
深入理解Java闭包概念 https://www.zhoulujun.cn/html/java/javaBase/7967.html
转载本站文章《从λ演算到函数式编程聊闭包(1):闭包概念在Java/PHP/JS中形式》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/js6/2015_0814_240.html
从λ演算到函数式编程聊闭包(1):闭包概念在Java/PHP/JS中形式的更多相关文章
- promise 的基本概念 和如何解决js中的异步编程问题 对 promis 的 then all ctch 的分析 和 await async 的理解
* promise承诺 * 解决js中异步编程的问题 * * 异步-同步 * 阻塞-无阻塞 * * 同步和异步的区别? 异步;同步 指的是被请求者 解析:被请求者(该事情的处理者)在处理完事情的时候的 ...
- JavaScript函数式编程究竟是什么?
摘要: 理解函数式编程. 作者:前端小智 原文:JS中函数式编程基本原理简介 Fundebug经授权转载,版权归原作者所有. 在长时间学习和使用面向对象编程之后,咱们退一步来考虑系统复杂性. 在做了一 ...
- paip.函数式编程方法概述以及总结
paip.函数式编程方法概述以及总结 1 函数式编程:函数式风格..很多命令式语言里支持函数式编程风格 1.1 起源 (图灵机,Lisp机器, 神经网络计算机) 1.2 函 ...
- [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念
本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程? java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的 ...
- 如何编写高质量的 JS 函数(3) --函数式编程[理论篇]
本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/EWSqZuujHIRyx8Eb2SSidQ作者:杨昆 [编写高质量函数系列]中, <如何 ...
- 可爱的 Python : Python中的函数式编程,第三部分
英文原文:Charming Python: Functional programming in Python, Part 3,翻译:开源中国 摘要: 作者David Mertz在其文章<可爱的 ...
- 可爱的 Python : Python中函数式编程,第二部分
英文原文:Charming Python: Functional programming in Python, Part 2,翻译:开源中国 摘要: 本专栏继续让David对Python中的函数式编 ...
- 简学Python第三章__函数式编程、递归、内置函数
#cnblogs_post_body h2 { background: linear-gradient(to bottom, #18c0ff 0%,#0c7eff 100%); color: #fff ...
- 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 引言&前言
原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 译者团队(排名不分先后):阿希.blueken.brucec ...
- 翻译连载 | 第 11 章:融会贯通 -《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇
原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...
随机推荐
- xxl-job默认accessToken命令执行漏洞复现
起因: 昨天看见微步发布XXL-JOB默认accessToken身份绕过漏洞,之前hw期间遇到过几次,都没弱口令和未授权,对其有点印象,遂复现一下. 漏洞影响:2.3.1和2.4 环境准备: 1.下载 ...
- JVM指令分析
代码: 1 public class AppGo{ 2 public static void test() { 3 boolean flag = true; 4 if (flag) System.ou ...
- 通义千问, 文心一言, ChatGLM, GPT-4, Llama2, DevOps 能力评测
引言 "克隆 dev 环境到 test 环境,等所有服务运行正常之后,把访问地址告诉我","检查所有项目,告诉我有哪些服务不正常,给出异常原因和修复建议",在过 ...
- 【源码解读(二)】EFCORE源码解读之查询都做了什么以及如何自定义批量插入
引言 书接上回,[源码解读(一)]EFCORE源码解读之创建DBContext查询拦截,在上一篇文章中,主要讲了DBContext的构造函数,以及如何缓存查询方法提升查询性能,还有最重要的拦截查询,托 ...
- linux锁定脚本防止脚本重复运行
问题描述:设置在定时任务中的脚本一定要注意防止脚本重复执行,要不然会带来一些想象不到的结果. 方式一:使用锁定文件的方式来进行防止脚本重复运行,类似数据库socket文件,但是这种情况有一种弊端就是, ...
- .NET周刊【11月第2期 2023-11-12】
国内文章 一个基于百度飞桨封装的.NET版本OCR工具类库 - PaddleOCRSharp https://www.cnblogs.com/Can-daydayup/p/17818557.html ...
- C语言一辆卡车撞人逃逸。现场三人目击事件,只记下车的一些特征。甲说:牌照的前两位数字是相同的;乙说:牌照的后两位数字是相同的;丙是位数学家说:四位的车号正好是一个整数的平方
#include <stdio.h> #include <math.h> void main() { int a, b, c, d, n, h; double t; for ( ...
- ij社区版如何创建spring项目
他们说是使用spring init什么什么的,那个都是老版的名称了,你去插件里面搜找是肯定搜不到的,现在叫spring boot helper,用这个,安装一下就好了(注意本次是在2022/11/1 ...
- VA01/VA02/VA03 销售订单根据定价和步骤校验权限隐藏价格(二)
1.文档说明 1.1.内容回顾 之前发表过相关文章<VA01/VA02/VA03 销售订单根据定价和步骤校验权限隐藏价格>,本篇文章对上一篇文章做补充说明. 第一篇文章是通过拥有权限,则隐 ...
- 从零玩转Nginx
01[熟悉]实际开发中的问题? 现在我们一个项目跑在一个tomcat里面 当一个tomcat无法支持高的并发量时.可以使用多个tomcat 那么这多个tomcat如何云分配请求 |-nginx 02[ ...