目錄html
假設如今咱們須要開發一個繪製數學函數平面圖像(一元)的工具庫,能夠提供繪製各類函數圖形的功能,好比直線f(x)=ax+b、拋物線f(x)=ax²+bx+c或者三角函數f(x)=asinx+b等等。那麼怎麼設計公開接口呢?因爲每種行數的係數(a、b、c等)不一樣,而且函數構造也不一樣。正常狀況下咱們很難提供一個統一的接口。因此會出現相似下面這樣的公開方法:編程
//繪製直線函數圖像 public void DrawLine(double a, double b) { List<PointF> points = new List<PointF>(); for(double x=-10;x<=10;x=x+0.1) { PointF p =new PointF(x,a*x+b); points.Add(p); } //將points點鏈接起來 } //繪製拋物線圖像 public void DrawParabola(double a, double b, double c) { List<PointF> points = new List<PointF>(); for(double x=-10;x<=10;x=x+0.1) { PointF p =new PointF(x,a*Math.Pow(x,2) + b*x + c); points.Add(p); } //將points點鏈接起來 } ... DrawLine(3, 4); //繪製直線 DrawParabola(1, 2, 3); //繪製拋物線
若是像上面這種方式着手的話,繪製N種不一樣函數就須要定義N個接口。很明顯不可能這樣去作。數組
(注,若是採用虛方法的方式,要繪製N種不一樣函數圖像就須要定義N個類,每一個類中都須要重寫生成points的算法)編程語言
若是咱們換一種方式去思考,既然是給函數繪製圖像,爲何要將它們的係數做爲參數傳遞而不直接將函數做爲參數傳給接口呢?是的,沒錯,要繪製什麼函數圖像,那麼咱們直接將該函數做爲參數傳遞給接口。因爲C#中委託就是對方法(函數,這裏姑且不討論二者的區別)的一個封裝,那麼C#中使用委託實現以下:函數式編程
public delegate double Function2BeDrawed(double x); //繪製函數圖像 public void DrawFunction(Function2BeDrawed func) { List<PointF> points = new List<PointF>(); for(double x=-10;x<=10;x=x+0.1) { PointF p =new PointF(x,func(x)); points.Add(p); } //將points點鏈接起來 } ... Function2BeDrawed func = (Function2BeDrawed)((x) => { return 3*x + 4;}); //建立直線函數 DrawFunction(func); //繪製係數爲三、4的直線 Function2BeDrawed func2 = (Function2BeDrawed)((x) => {return 1*Math.Pow(x,2) + 2*x + 3;}); //建立拋物線函數 DrawFunction(func2); //繪製係數爲一、二、3的拋物線 Function2BeDrawed func3 = (Function2BeDrawed)((x) => {return 3*Math.Sin(x) + 4;}); //建立正弦函數 DrawFunction(func3); //繪製係數爲三、4的正弦函數圖像
如上。將函數(委託封裝)做爲參數直接傳遞給接口,那麼接口就能夠統一。至於到底繪製的是什麼函數,徹底由咱們在接口外部本身肯定。函數
將函數看做和普通類型同樣,能夠對它賦值、存儲、做爲參數傳遞甚至做爲返回值返回,這種思想是函數式編程中最重要的宗旨之一。工具
注:上面代碼中,若是以爲建立委託對象的代碼比較繁雜,咱們能夠本身再定義一個函數接收a、b兩個參數,返回一個直線函數,這樣一來,建立委託的代碼就不用重複編寫。學習
在函數式編程中,咱們將函數也看成一種類型,和其餘普通類型(int,string)同樣,函數類型能夠賦值、存儲、做爲參數傳遞甚至能夠做爲另一個函數的返回值。下面分別以C#和F#爲例簡要說明:
注:F#是.NET平臺中的一種以函數式編程範式爲側重點的編程語言。舉例中的代碼很是簡單,沒學過F#的人也能輕鬆看懂。F#入門看這裏:MSDN
定義:
在C#中,咱們定義一個整型變量以下:
int x = 1;
在F#中,咱們定義一個函數以下:
let func x y = x + y
賦值:
在C#中,咱們將一個整型變量賦值給另一個變量:
int x = 1;
int y = x;
在F#中,咱們照樣能夠將函數賦值給一個變量:
let func = fun x y -> x + y //lambda表達式
let func2 = func
存儲:
在C#中,咱們能夠將整型變量存儲在數組中:
int[] ints = new int[]{1, 2, 3, 4, 5};
在F#中,咱們照樣能夠相似的存儲函數:
let func x = x + 1
let func2 x = x * x
let func3 = fun x -> x - 1 //lambda表達式
let funcs = [func; func2; func3] //存入列表,注意存入列表的函數簽名要一致
傳參:
在C#中將整型數值做爲參數傳遞給函數:
void func(int a, int b)
{
//
}
func(1, 2);
在F#中將函數做爲參數傳遞給另一個函數:
let func x = x * x //定義函數func
let func2 f x = //定義函數func2 第一個參數是一個函數
f x
func2 func 100 //將func和100做爲參數 調用func2
做爲返回值:
在C#中,一個函數返回一個整型:
int func(int x)
{
return x + 100;
}
int result = func(1); //result爲101
在F#中,一個函數返回另一個函數:
let func x =
let func2 = fun y -> x + y
func2 //將函數func2做爲返回值
let result = (func 100) 1 //result爲101,括號能夠去掉
函數式編程由Lambda演算得來,所以它與咱們學過的數學很是相似。在學習函數式編程以前,咱們最好忘記以前頭腦中的一些編程思想(如學習C C++的時候),由於先後兩個編程思惟徹底不一樣。下面分別舉例來講明函數式編程中的一些概念和數學中對應概念關係:
注:關於函數式編程的特性(features)網上總結有不少,能夠在這篇博客中看到。
1.函數定義
數學中要求函數必須有自變量和因變量,因此在函數式編程中,每一個函數必須有輸入參數和返回值。你能夠看到F#中的函數不須要顯示地使用關鍵字return去返回某個值。因此,那些只有輸入參數沒有返回值、只有返回值沒有輸入參數或者二者都沒有的函數在純函數式編程中是不存在的。
2.無反作用
數學中對函數的定義有:對於肯定的自變量,有且僅有一個因變量與之對應。言外之意就是,只要輸入不變,那麼輸出必定固定不變。函數式編程中的函數也符合該規律,函數的執行既不影響外界也不會被外界影響,只要參數不變,返回值必定不變。
3.柯里化
函數式編程中,能夠將包含了多個參數的函數轉換成多個包含一個參數的函數。好比對於下面的函數:
let func x y = x + y
let result = func 1 2 //result爲3
能夠轉換成
let func x =
let func2 = fun y -> x + y
func2
let result = (func 1) 2 //result結果也爲3,能夠去掉括號
能夠看到,一個包含兩個參數的函數通過轉換,變成了只包含一個參數的函數,而且該函數返回另一個接收一個參數的函數。最後調用結果不變。這樣作的好處即是:講一個複雜的函數能夠分解成多個簡單函數,而且函數調用時能夠逐步進行。
其實同理,在數學中也有相似「柯里化」的東西。當咱們計算f(x,y) = x + y這個函數時,咱們能夠先將x=1帶入函數,獲得的結果爲f(1,y) = 1 + y。這個結果顯然是一個關於y的函數,以後咱們再將y=2帶入獲得的函數中,結果爲f(1,2) = 1 + 2。這個分步計算的過程其實就是相似於函數式編程中的「柯里化」。
4.不可變性
數學中咱們用符號去表示一個值或者表達式,好比「令x=1」,那麼x就表明1,以後不能再改變。同理,在純函數式編程中,不存在「變量」的概念,也沒有「賦值」這一說,全部咱們以前稱之爲「變量」的東西都是標識符,它僅僅是一個符號,讓它表示一個東西以後不能再改變了。
5.高階函數
在函數式編程中,將參數爲函數、或者返回值爲函數的這類函數統稱之爲「高階函數」,前面已經舉過這樣的例子。在數學中,對一個函數求導函數的過程,其實就是高階函數,原函數通過求導變換後,獲得導函數,那麼原函數即是輸入參數,導函數即是返回值。
過程式、面向對象再到這篇文章講到的函數式等,這些都是不一樣地編程範式。每種範式都有本身的主導編程思想,也就是對待同一個問題思考方式都會不一樣。很明顯,學會多種範式的編程語言對咱們思惟方式有很是大的好處。
不管是本文中舉例使用到的F#仍是Java平臺中的Scala,大多數冠名「函數式編程語言」的計算機語言都並非純函數式語言,而是以「函數式」爲側重點,同時兼顧其餘編程範式。就連曾經主打「面向對象」的C#和Java,現現在也慢慢引入了「函數式編程風格」。C#中的委託、匿名方法以及lambda表達式等等這些,都讓咱們在C#中進行函數式編程成爲可能。若是須要遍歷集合找出符合條件的對象,咱們之前這樣去作:
foreach(Person p in list)
{
if(p.Age > 25)
{
//...
}
}
如今能夠這樣:
list.Where(p => p.Age>25).Select(p => p.Name).toArray();
本篇文章開頭提出的問題,採用C#委託的方式去解決,其實本質上也是函數式思想。因爲C#必須遵循OO準則,因此引入委託幫助咱們像函數式編程那樣去操做每一個函數(方法)。
本篇文章介紹有限,並無充分說明函數式編程的優勢,好比它的不可變特性無反作用等有利於並行運算、表達方式更利於人的思惟等等。實質上博主本人並無參與過實際的採用函數式語言開發的項目,可是博主認爲函數式思想值得咱們每一個人去了解、掌握。(本文代碼手敲未驗證,若有拼寫錯誤見諒)