之劍 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>