編程語言範式html
常見的編程範式有命令式編程(Imperative programming),函數式編程,邏輯式編程;sql
許多現存的編程語言均可基於其計算模型加以分類,納入某些語言族,或者屬於某種編程範式。按照不一樣的規則,能夠有多種分類的方法,並且不一樣的學者對某些語言的具體歸屬也有不一樣的意見。編程
給出一種系譜:數組
說明式(Declarative ) 命令式( Imperative )緩存
函數式 Lisp, ML, Haskell 馮諾依曼 C, Ada, Fortran閉包
數據流 ld, Val 腳本式 Perl, Python, PHP架構
邏輯式 Prolog 面向對象 Smalltalk, C++, Java, C#編程語言
基於模板 XSLT函數式編程
有些編程範式並不能按以上的方法進行分類,好比:元編程,泛型編程。函數
一種語言並非只從屬於一種編程範式,有些語言自己就是爲支持多範式設計的;
好比:Lisp就同時支持函數式編程、面向對象、元編程。
命令式編程是面向計算機硬件的抽象,有變量(對應着存儲單元),賦值語句(獲取,存儲指令)表達式(內存引用和算術運算)和控制語句(跳轉指令);
函數式編程
定義
In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data.
函數式編程是一種編程模型,他將計算機運算看作是數學中函數的計算,而且避免了狀態以及變量的概念。<Wiki>
函數式編程是面向數學的抽象,將計算描述爲一種表達式求值,一句話,函數式程序就是一個表達式。
函數式編程最重要的基礎是 λ 演算(lambda calculus)。並且λ演算的函數能夠接受函數看成輸入(參數)和輸出(返回值)。和指令式編程相比,函數式編程的函數的計算比指令的執行更重要。和過程化編程相比,函數式編程的函數的計算可隨時調用。純函數式編程不須要變量;
語言族
函數式編程中最古老的例子可能要數1958年被創造出來的LISP,當年的Lisp因爲各類設計缺陷(內存損耗、閉包問題、生成程序執行效率等)沒能發展好。較現代的例子包括Haskell、Clean、Erlang 和Miranda等。
現代編程語言,如C#、Python、Ruby、Scala等等,它們都受到了函數式編程語言的影響,好比C#中的lamada表達式、Linq。
基於JVM實現的Lisp方言如Scala, Clojure也是愈來愈受關注,這裏所謂的Lisp方言,主要是由於語法上沿用了Lisp中的S表達式。
基於.net平臺的有F#,微軟的首個函數式編程語言。<MSDN>
不一樣語言的抽象層次
高 計算
C# -----> 對象
Python -----> 函數式
C語言 -----> 函數 (面向過程)
彙編語言
低 計算機硬件 -----> 指令 計算機
函數式復興
Anders Hejlsberg,C#編程語言的首席架構師,2010年關於《編程語言的發展趨勢及將來方向》演講
從一個數組中找出全部的偶數
List<int> list = new List<int> { 1,2,3,4,5,6,7};
常規的命令式寫法:
List<int> ret = new List<int>();
foreach (var item in list)
{ if (item % 2 == 0)
ret.Add(item); }
聲明式的寫法: var ret = list.Where((x) => x % 2 == 0);
多核與並行
使用命令式編程語言寫程序時,咱們常常會編寫如x = x + 1這樣的語句,此時咱們大量依賴的是可變狀態,或者說是「變量」,它們的值能夠隨程序運行而改變。可變狀態很是強大,但隨之而來的即是被稱爲「反作用」的問題,例如一個無需參數的void方法,它會根據調用次數或是在哪一個線程上進行調用對程序產生影響,它會改變程序內部的狀態,從而影響以後的運行效果。而在函數式編程中則不會出現這個狀況,由於全部的狀態都是不可變的。事實上對函數式編程的討論更像是數學、公式,而不是程序語句,如x = x + 1對於數學家來講,彷佛只是個永不爲真的表達式而已。
函數式編程十分容易並行,由於它在運行時不會修改任何狀態,所以不管多少線程在運行時均可以觀察到正確的結果。假如兩個函數徹底無關,那麼它們是並行仍是順序地執行便沒有什麼區別。
函數式編程特性 與技術
函數是一等公民 閉包
高階函數 惰性求值
遞歸 緩存技術
不可變狀態 尾調用消除
柯里化 內存回收
C#函數式支持
Linq涉及的C#語言特性:隱式類型、匿名類型、初始化器、迭代器、委託、泛型、泛型委託、匿名方法、Lamada表達式。
函數對象必須是某種委託類型. 在C#中,咱們能夠定義強類型的委託類型或泛型的委託類型,委託能夠表明跟這個委託類型有相同參數的方法(靜態方法,類方法)的引用.
在使用LINQ的時候咱們能夠常常看到高階函數。舉個例子,若是你想將一個已有的序列使用一些函數轉換爲一個新的序列,你將使用相似LINQ的select函數(函數做爲輸入):
var squares = numbers.Select( num => num*num );
函數是一等公民
對象是面向對象的第一型,那麼函數式編程也是同樣,函數是函數式編程的第一型。
咱們在函數式編程中努力用函數來表達全部的概念,完成全部的操做。
在面向對象編程中,咱們把對象傳來傳去,那在函數式編程中,咱們要作的是把函數傳來傳去。
函數這個術語不是指計算機中的函數,而是指數學中的函數,即自變量的映射。
函數能夠在任何地方定義,在函數內或函數外,能夠做爲函數的參數和返回值,能夠對函數進行組合。
高階函數:能接收函數作參數的函數。
1:函數自身接受一個或多個函數做爲輸入參數;
2:函數自身能輸出(返回)一個函數;
不可變狀態
純函數式編程語言中的變量也不是命令式編程語言中的變量,即存儲狀態的單元,而是代數中的變量,即一個值的名稱。變量的值是不可變的;
函數即不依賴外部的狀態也不修改外部的狀態,函數調用的結果不依賴調用的時間和位置,使得單元測試和調試都更容易。
遞歸
因爲變量不可變,純函數編程語言沒法實現循環,這是由於For循環使用可變的狀態做爲計數器,而While循環或DoWhile循環須要可變的狀態做爲跳出循環的條件。所以在函數式語言裏就只能使用遞歸來解決迭代問題,這使得函數式編程嚴重依賴遞歸。
遞歸定義的計算的Scala代碼以下:
def fact(n: Int):Int= {
if(n == 0) return 1
n * fact(n-1)
}
C#代碼
Public int Fact(int n)
{
int acc = 1;
for(int k = 1; k <= n; k++){
acc = acc * k;}
}
尾遞歸
若是一個函數中全部遞歸形式的調用都出如今函數的末尾,咱們稱這個遞歸函數是尾遞歸的。當遞歸調用是整個函數體中最後執行的語句且它的返回值不屬於表達式的一部分時,這個遞歸調用就是尾遞歸。尾遞歸函數的特色是在迴歸過程當中不用作任何操做,這個特性很重要,由於大多數現代的編譯器會利用這種特色自動生成優化的代碼(將尾遞歸轉化爲迭代);
柯里化(Currying)和部分(偏)函數(Partial Function)
是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數且返回結果的新函數的技術。
主要功能是提供了強大的動態函數建立方法,經過調用另外一個函數併爲它傳入要柯里化(currying)的函數和必要的參數而獲得。通俗點說就是利用已有的函數,再建立一個動態的函數,該動態函數內部仍是經過已有的函數來發生做用。
柯里化就是一個函數在參數沒給全時返回另外一個函數,返回的函數的參數正好是餘下的參數。好比:你制定了x和y, 如2的3次方,就返回8, 若是你只制定x爲2,y沒指定, 那麼就返回一個函數:2的y次方, 這個函數只有一個參數:y。
curry就是對高階函數(就是一種對過程的抽象 參考map它就是一個抽象的過程)的降階處理。
2大特性:
將多個參數的函數進行拆分,拆成多個只有一個參數的函數。爲何要拆分,λ 演算。
示例:
常規的寫法:Func<int, int, int> Add = (x, y) => x + y;
拆分: Func<int, Func<int, int>> Add = x => y => x + y;
輸入一個參數,返回一個具備一個參數的函數,接着再調用返回的函數,就完成整個調用。
調用:
var add2 = Add(3);
var ret = add2(4);
寫成一行:
var ret = Add(3)(4);
或者不重寫,只要爲原來的方法加一個擴展方法:
public static Func<T1,Func<T2,T3>> Currey<T1,T2,T3>(this Func<T1,T2,T3> func)
{
return x => y => func(x,y);
}
這樣就能夠對C#標準的GenralAdd(int x,int y)方法執行Currey轉換爲部分(偏)函數了:
Func<int, int, int> Add = GenralAdd;
var CurreyedAdd = Add.Currey()(3)(4);
示例:好比咱們常常須要執行SQL語句,固然須要使用SqlConnection,而後附加上對應的SQL語句,爲此咱們能夠開發一個簡單的函數,用來簡化這一過程:
Func<SqlConnection, Func<String, DataSet>> ExecSql = x => y =>
{
using (x)
{
x.Open();
var com = x.CreateCommand();
DataSet ds = new DataSet();
com.CommandText = y;
SqlDataAdapter adapter = new SqlDataAdapter(com);
adapter.Fill(ds);
return ds;
}
};
調用:
var esql = ExecSql(new SqlConnection("xxx"));
var rds = esql("select xxxx from xxx");
rds = esql("select ffff from ffff");
若是想先傳入Sql語句再傳入SqlConnection:
Func<String, Func<SqlConnection, DataSet>> ExecSqlT = x => y => ExecSql(y)(x);
看一個函數:
static Func<int, int> GetAFunc()
{
var myVar = 1;
Func<int, int> inc = delegate(int var1)
{
myVar = myVar + 1;
return var1 + myVar;
};
return inc;
}
以下調用輸入什麼結果:
var inc = GetAFunc();
Console.WriteLine(inc(5));
Console.WriteLine(inc(6));
閉包
閉包是能夠包含自由(未綁定到特定對象)變量的代碼塊;這些變量不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義(局部變量)。
「閉包」 一詞來源於如下二者的結合:要執行的代碼塊(因爲自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和爲自由變量提供綁定的計算環境(做用域)。<百度百科>
C#中實現閉包,實際就是經過類,封裝變量和方法,提高生命週期。
static void Closure1()
{
List<Action> actions = new List<Action>();
for (int i = 0; i < 10; i++)
{
int copy = i;
actions.Add(() => Console.WriteLine(copy));
}
foreach (Action action in actions)
action();
}
static void Closure2()
{
int copy;
List<Action> actions = new List<Action>();
for (int i = 0; i < 10; i++)
{
copy = i;
actions.Add(() => Console.WriteLine(copy));
}
foreach (Action action in actions)
action();
}
緩存技術
怎樣使用閉包來實現緩存。若是咱們建立了一個用於緩存的接收函數就能夠實現緩存,並返回一個在一段時間內緩存結果的新函數。如下列表顯示的例子:
public static Func<T> Cache<T>(this Func<T> func, int cacheInterval)
{
var cachedValue = func();
var timeCached = DateTime.Now;
Func<T> cachedFunc = () => {
if ((DateTime.Now - timeCached).Seconds >= cacheInterval)
{
timeCached = DateTime.Now;
cachedValue = func();
}
return cachedValue;
};
return cachedFunc;
}
變量 cacheInterval, cachedValue 和 timeCached 綁定到緩存的函數並做爲函數的一部分。這個可讓咱們記住最後的值並確認被緩存多長時間。
下面的例子中咱們能夠看到如何使用這個擴展來緩存函數值和返回當前時間:
Func<DateTime> now = () => DateTime.Now;
Func<DateTime> nowCached = now.Cache(4);
Console.WriteLine("\tCurrent time\tCached time");
for (int i = 0; i < 20; i++)
{
Console.WriteLine("{0}.\t{1:T}\t{2:T}", i + 1, now(), nowCached());
Thread.Sleep(1000);
}
惰性求值
C#語言小部分採用了非嚴格求值策略,大部分仍是嚴格求值策略。
非嚴格求值的例子:邏輯或
static void NonStrictEvaluation()
{
bool ret = true || DoSomeThing() > 0;
Console.WriteLine("Done!");
}
嚴格求值策略:首先定義一個返回Int的方法
static int DoSomeThing()
{
Console.WriteLine("DoSomeThing Function Excuted");
return 7;
}
static void StrictEvaluation(bool flag, int dsVal)
{
if (flag)
Console.WriteLine("dsVal result value is {0}", dsVal);
Console.WriteLine("Done!");
}
調用:StrictEvaluation(false, DoSomeThing());
輸出:
DoSomeThing Function Excuted
Done!
雖然flag爲false,可是DoSomeThing仍是被執行了,如何改變?
將第二個參數改爲方法:
static void LazyEvaluation(bool flag,Func<int> dsthing)
{
if (flag)
Console.WriteLine("dsthing result value is {0}", dsthing());
Console.WriteLine("Done!");
}
調用:StrictEvaluation(false, DoSomeThing);
若是flag爲true,而且其中調用兩次,那麼DoSomeThing就會被執行兩次。再次修改
static void LazyEvaluationEx(bool flag, Func<int> dsthing)
{
Lazy<int> lzDshting = new Lazy<int>(dsthing);
if (flag)
Console.WriteLine("dsthing square result value is {0}", lzDshting.Value * lzDshting.Value);
Console.WriteLine("Done!");
}