之剑 javascript
2016.5.2 11:19:09java
For example, in Lisp the 'square' function can be expressed as a lambda expression as follows:算法
(lambda (x) (* x x))
“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式能够表示闭包(注意和数学传统意义上的不一样)。express
Lambda calculus (also written as λ-calculus) is a formal system in mathematical logic for expressing computation based on function abstraction and application using variable binding and substitution. It was first introduced by mathematician Alonzo Church in the 1930s as part of an investigation into the foundations of mathematics. Lambda calculus is a universal model of computation equivalent to a Turing machine (Church-Turing thesis, 1937). Its namesake, Greek letter lambda (λ), is used in lambda terms (also called lambda expressions) to denote binding a variable in a function.编程
Lambda calculus may be typed and untyped. In typed lambda calculus functions can be applied only if they are capable of accepting the given input's "type" of data.数组
Lambda calculus has applications in many different areas in mathematics, philosophy,linguistics, and computer science. Lambda calculus has played an important role in the development of the theory of programming languages. Functional programming languages implement the lambda calculus. Lambda calculus also is a current research topic in Category theory.闭包
Lambda演算和函数式语言的计算模型天生较为接近,Lambda表达式通常是这些语言必备的基本特性。如:app
Scheme:编程语言
(lambda(x)(+x1))
Haskell:ide
\x->x+1
λ演算是一套用于研究函数定义、函数应用和递归的形式系统。函数的做用 (application) 是左结合的:
f x y = (f x) y
它包括一条变换规则 (变量替换) 和一条函数定义方式.
好比说,下面的这个圆锥曲线二元二次函数:
$$f(x,y)=x^2+y^2$$
让咱们用其余的一些方式来表达:
$$(x,y) rightarrow x^2 + y^2$$
$$x rightarrow (y rightarrow x^2 + y^2 )$$
为了更加简单的理解其演算过程, 分解各个步骤以下:
数学函数表达式: \(f(5,2) = 5^2 + 2^2 = 29\)
匿名函数式:\( ((x,y) rightarrow (x^2 + y^2)) (5,2) = 5^2 + 2^2 = 29\)
柯里化函数式: \( (x rightarrow ( y rightarrow (x^2 + y^2) )(5))(2)=(y rightarrow (5^2+y^2))(2) = 5^2+2^2=29 \)
λ演算由 Alonzo Church 和 Stephen Cole Kleene 在 20 世纪三十年代引入,Church 运用 lambda 演算在 1936 年给出 断定性问题 (Entscheidungsproblem) 的一个否认的答案。这种演算能够用来清晰地定义什么是一个可计算函数。关于两个 lambda 演算表达式是否等价的命题没法经过一个通用的算法来解决,这是不可断定性可以证实的头一个问题,甚至还在停机问题之先。Lambda 演算对函数式编程有巨大的影响,特别是Lisp 语言。
在 lambda 演算中有许多方式均可以定义天然数,但最多见的仍是Church 整数,下面是它们的定义:
$$ 0 = lambda fcdot lambda xcdot x $$
$$ 1 = lambda fcdot lambda xcdot f x $$
$$ 2 = lambda fcdot lambda xcdot f (f x ) $$
$$ 3 = lambda fcdot lambda xcdot f(f (f x )) $$
$$...$$
$$ n = lambda fcdot lambda xcdot f^n( x ) $$
以此类推。直观地说,lambda 演算中的数字 n 就是一个把函数 f 做为参数并以 f 的 n 次幂为返回值的函数。换句话说,Church 整数是一个高阶函数 :
以单一参数函数 f 为参数,返回另外一个单一参数的函数。
(注意在 Church 原来的 lambda 演算中,lambda 表达式的形式参数在函数体中至少出现一次,这使得咱们没法像上面那样定义 0)
C#的Lambda 表达式都使用 Lambda 运算符 =>,该运算符读为“goes to”。语法以下:
形参列表=>函数体 (input parameters) => expression (input parameters) => {statement;}
函数体多于一条语句的可用大括号括起。例子:
() => Console.Write("0个参数") I => Console.Write("1个参数时参数列中可省略括号,值为:{0}",i) (x,y) => Console.Write("包含2个参数,值为:{0}*{1}",x,y) //两条语句时必需要大括号 I => { i++;Console.Write("两条语句的状况"); }
Lambda 表达式是一种可用于建立委托或表达式目录树类型的匿名函数。 经过使用 lambda 表达式,能够写入可做为参数传递或做为函数调用值返回的本地函数。 Lambda 表达式对于编写 LINQ 查询表达式特别有用。
若要建立 Lambda 表达式,须要在 Lambda 运算符 => 左侧指定输入参数(若是有),而后在另外一侧输入表达式或语句块。 例如,lambda 表达式 x => x * x 指定名为 x 的参数并返回 x 的平方值。
过滤条件:
List<User> users = new List<User>(); Func<User, bool> predicate = ((user) => { return user.UserId > 100; } ); List<User> temps = users.Where(predicate).ToList();
Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。下面这个例子就是使用Lambda语法来代替匿名的内部类,代码不只简洁,并且还可读。
没有使用Lambda的老方法:
Runnable r = new Runnable(){ @Override public void run(){ System.out.println("Running without Lambda"); } };
使用Lambda表达式:
Runnable r =() -> { System.out.println("Running without Lambda"); };
ISO C++ 11 标准的一大亮点是引入Lambda表达式。基本语法以下:
[capture list] (parameter list) ->return type { function body }
其中除了“[ ]”(其中捕获列表能够为空)和“复合语句”(至关于具名函数定义的函数体),其它都是可选的。它的类型是惟一的具备成员operator()的非联合的类类型,称为闭包类型(closure type)。
C++中,一个lambda表达式表示一个可调用的代码单元。咱们能够将其理解为一个未命名的内联函数。它与普通函数不一样的是,lambda必须使用尾置返回来指定返回类型。
scala的匿名函数使用很是的普遍,这也是函数式语言的标志之一。
scala中的集合类List有一个map方法,该方法接收一个函数做为参数,对List中的每个元素使用参数指定的方法,此处很是适合用匿名函数,请看下面代码:
val l=List(1,2,3) l.map(i=>i+9)
上面代码的第二行map函数的参数就是一个匿名函数,或者是lambda表达式。
i=>i+9中的=>是参数列表和返回值的分隔符,若是少于两个参数能够不写小括号,后面部分是函数的返回值。若是函数体包含多行代码可使用花括号,例如:
l.map((i)=>{ println("HI"); i+9 })
定义常规的scala add函数:
def add (i:Int,j:Int):Int=i+j
这个函数太简单了.
咱们定义一个devide函数实现除法,通常咱们会这么写:
// 多参数的写法 def multiply(x: Int, y: Int) = x * y
Moses Schnfinkel 和 Gottlob Frege 发明了以下的表达方式:
def multiply(x: Int)(y: Int) => x * y
这个函数和上面的函数不一样之处在于它的参数列表,是一个参数一个小括号,不是把全部参数都放到一个括号内的。下面咱们经过实际的例子来理解scala的curried。
咱们要定义一个乘法函数:
// 柯里化的写法 def multiply(x: Int)(y: Int) = x * y // 计算两个数的乘积 multiply(6)(7)
multiply(6)返回的是函数(y: Int)=>6*y,再将这个函数应用到7
(6 * y ) (7) = 42
最终获得结果42.
这就是scala的乘法函数了.
curried不太好理解,enjoy it!函数柯里化的主要功能是提供了强大的动态函数建立方法,经过调用另外一个函数并为它传入要柯里化(currying)的函数和必要的参数而获得。通俗点说就是利用已有的函数,再建立一个动态的函数,该动态函数内部仍是经过已有的函数来发生做用.
咱们在想, 这些计算机科学家,数学家们脑子里每天都在想些什么? 尽发明一些普通人很差理解的概念. 可是, 存在即合理.既然这玩意存在着,一定代表必有其存在之意义.
多参数是个虚饰,不是编程语言的根本性的特质。利用柯里化把某个函数参数单独拎出来,提供更多用于类型推断的信息.
在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数且返回结果的新函数的技术。这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
在直觉上,柯里化声称“若是你固定某些参数,你将获得接受余下参数的一个函数”。因此对于有两个变量的函数yx,若是固定了 y = 2,则获得有一个变量的函数 2x。
在理论计算机科学中,柯里化提供了在简单的理论模型中好比只接受一个单一参数的lambda 演算中研究带有多个参数的函数的方式。
柯里化特性决定了它这应用场景。提早把易变因素,传参固定下来,生成一个更明确的应用函数。最典型的表明应用,是bind函数用以固定this这个易变对象。
Function.prototype.bind = function(context) { var _this = this, _args = Array.prototype.slice.call(arguments, 1); return function() { return _this.apply(context, _args.concat(Array.prototype.slice.call(arguments))) } }
柯里化其实自己是固定一个能够预期的参数,并返回一个特定的函数。这增长了函数的适用性,但同时也下降了函数的适用范围。这个思路有点像微积分里面的多重积分的处理方式.
柯里化的本质就是函数参数单一化. 由于多参数有其问题.你在写不少高阶函数的时候,你都得考虑你的 参数函数f 在面临各类传参方式的状况下要怎么处理,这事儿是痛苦而且容易忘记的,由于你本不该该关心f的参数是些啥的!盲目地引入传递多参的机制,实际上是没有把问题想明白的表现。语法层面支持的多参函数定义是一种扁平、简单、有效的方式,可是,当程序须要作高阶函数抽象的时候,这种方式会带来麻烦。
经过组合型数据来传递复杂参数,是一种很天然的方式,这种状况下并不须要刻意考虑“多参函数”这种问题,而且这种方式对于作高阶函数抽象很是友好。
科里化的方式定义多参函数,一样是一种天然产生的方式,只要把函数当作一等公民,就会存在科里化的方式。科里化的方式使得咱们的函数能够更细粒度地方便地组合。
可是,是否要使用科里化的方式,倒是一件须要细细揣摩的事情。好比,我要定义一个rotate函数,它接受一个二维向量的坐标,返回一个逆时针旋转九十度后的向量坐标,那么你可能会把它定义成
rotate = (\x -> (\y -> (y, -x)))
或者
rotate = (\(x, y) -> (y, -x))
你以为哪一种定义方式比较好呢? 在这个例子里,显然是第二种定义方式比较好,
一来,二维向量的两个坐标一般是成对出现的,不多会有“部分应用”的须要,因此也就天然用不到科里化的任何好处;
二来,当你须要把一个向量rotate两次的时候,科里化版本就会体现出异常的麻烦了。
因此,结论是:
若是某些参数在大部分状况下,老是须要同时给出,才具备真实的意义,那么应该把这些参数组合成一个组合型参数来处理,而不该该科里化。 若是要定义的多参函数是一个闭合函数,那么它是极可能须要被屡次应用的,这种状况下,应该用组合型参数的方式来处理。 若是先指定某一些参数就有明确的意义,那么就应该用科里化的方式来处理。
在F#里,各类函数的signature 都是 int -> int -> int ->什么的……
what does that even mean??
Currying和partial application对于functional programming有怎样的真正的威力?
currying和partial application会affect performance吗?
函数不必定须要名称:
(x: Double) => 3 * x // 该匿名函数将传给它的参数乘3
能够将匿名函数赋值给变量,也能够当参数传递。
以Python内置的求绝对值的函数abs()为例,调用该函数用如下代码:
abs(-10) 10
可是,若是只写abs呢?
abs <built-in function abs>
可见,abs(-10)是函数调用,而abs是函数自己。
要得到函数调用结果,咱们能够把结果赋值给变量:
x = abs(-10) x 10
可是,若是把函数自己赋值给变量呢?
f = abs f <built-in function abs>
结论:函数自己也能够赋值给变量,即:变量能够指向函数。
若是一个变量指向了一个函数,那么,能否经过该变量来调用这个函数?用代码验证一下:
f = abs f(-10) 10
成功!说明变量f如今已经指向了abs函数自己。
那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,彻底能够把函数名abs当作变量,它指向一个能够计算绝对值的函数!
既然变量能够指向函数,函数的参数能接收变量,那么一个函数就能够接收另外一个函数做为参数,这种函数就称之为高阶函数。
一个最简单的高阶函数:
def add(x, y, f): return f(x) + f(y)
当咱们调用add(-5, 6, abs)时,参数x,y和f分别接收-5,6和abs,根据函数定义,咱们能够推导计算过程为:
x ==> -5 y ==> 6 f ==> abs f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
用代码验证一下:
add(-5, 6, abs) 11
编写高阶函数,就是让函数的参数可以接收别的函数。
把函数做为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=default"></script>