編程語言範式 常見的編程範式有命令式編程(Imperative programming),函數式編程,邏輯式編程; 許多現存的編程語言都可基於其計算模型加以分類,歸入某些語言族,或者屬於某種編程範式。按照不同的規則,可以有多種分類的方法,而且不同的學者對某些語言的具體歸屬也有不同的意見。 給出一種系 ...
編程語言範式
常見的編程範式有命令式編程(Imperative programming),函數式編程,邏輯式編程;
許多現存的編程語言都可基於其計算模型加以分類,歸入某些語言族,或者屬於某種編程範式。按照不同的規則,可以有多種分類的方法,而且不同的學者對某些語言的具體歸屬也有不同的意見。
給出一種系譜:
說明式(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大特性:
- 匿名函數
- 每個函數只有1個參數
將多個參數的函數進行拆分,拆成多個只有一個參數的函數。為什麼要拆分,λ 演算。
示例:
常規的寫法: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!");
}
參考 http://www.cnblogs.com/yaozhenfa/category/652982.html