學習 FC++:C++ 函數編程庫

作者: Arpan Sen  來源: IBM  發布時間: 2010-11-19 09:48  閱讀: 1034 次  推薦: 0   原文鏈接   [收藏]  
摘要:本文嘗試討論 C++ 的不同方面 — 通過 Yannis Smaragdakis 和 Brian McNamara 提供的開放源碼 FC++ 庫用 C++ 實現函數編程。學習如何使用 FC++ 實現基本的函數編程。

  為什么要實現函數編程,尤其是使用 FC++實現?

  與 OOP 等其他編程模型相比,函數編程具有一些優點:

  1.代碼簡潔

  2.編程沒有副作用(沒有通過 set/get 例程操縱的全局/靜態變量)

  3.可以進行快速的原型設計

  4.FC++提供大量語法和庫函數,幫助 Haskell 程序員順利完成轉換

  通過使用庫您并不能回避 C++本身沒有任何函數編程構造這一事實。FC++是最好的基于 C++的函數編程庫開放源碼實現,可以把它插入遺留的 C++代碼中。BSFC++等項目中已經使用了 FC++,BSFC++是一個用 C++進行函數大規模同步并行編程的庫。

  下載和安裝

  可以從 SourceForge 下載 FC++(見參考資料)。解壓壓縮的安裝文件(.zip)會產生一組頭文件。只需在用戶應用程序源代碼中包含 prelude.h 頭文件,就可以開始使用 FC++。清單 1 說明如何編譯使用 FC++代碼的源代碼。注意,這是依賴于頭文件的安裝,不涉及其他庫。

清單 1.編譯使用 FC++代碼的源代碼

 
g++ user_source1.cpp –I<path to FC++ installation>

  注意:本文中的所有代碼已經用 FC++ 1.5 和 g++ 3.4.4 測試過。
  理解 CFunType

  函數編程模型允許函數接受其他函數作為參數。顯然,基本的 C/C++不允許這種語法。為了解決這個問題,FC++函數表示為符合特定編碼約定的類的實例,這就是 CFunType 發揮作用的地方。C++函數對象的特征是在類定義中有 operator ()。清單 2 給出一個示例。

清單 2. C++函數對象的典型用法

 
struct square {
int operator()(int x){ return x*x; }
};

square sqr1;

int result = sqr1(5);

  清單 2 中的實現的問題在于,準確地說,sqr1 的函數類型是 int —> int,但是 sqr1 的 C++類型是 struct square。FC++引入了模板 CFunType,使用它對類型簽名信息進行編碼。CFunType 的最后一個參數是函數的返回類型,其他參數是輸入類型信息,其次序與函數原型中的次序相同。清單 3 說明如何使用 CFunType 對 square 函數進行編碼。

清單 3.使用 CFunType 對求平方操作的函數簽名進行編碼

 
#include "prelude.h"

struct square : public CFunType<int, int> {
int operator()(int x){ return x*x; }
};

square sqr1;

int result = sqr1(5);

  清單 4 是另一個示例,它在列表中插入一個整數并返回更新后的列表。

清單 4.使用 CFunType 對列表操作的函數簽名進行編碼

 
#include "prelude.h"
struct Insert : public CFunType<int,List<int>,List<int> > {
List
<int> operator()( int x, const List<int>& l ) const {
// code for determining where to insert the data goes here
}

  注意:清單 4 中的 List 數據類型是預定義的 FC++類型。

  把函數轉換為對象

  為了讓函數接受函數作為輸入參數,必須把函數轉換為對象。FC++在 CFunType 和 ptr_to_fun 例程的基礎上定義 FunN 類別的類,由 ptr_to_fun 例程實際執行轉換。請看一下清單 5。

清單 5.使用 ptr_to_fun 把函數轉換為 FC++函數對象

 
int multiply(int m, int n){ return m*n; }

Fun2
<int, int, int> mult1 = ptr_to_fun (&multiply);
int result = mult1(8, 9);

// result equals 72

  與 CFunType 一樣,Fun2 的簽名暗示這個對象代表一個函數,此函數接受兩個整數輸入并返回一個整數。同樣,Fun3<int, double, double, string> 代表的函數接受一個整數和兩個雙精度數,返回一個字符串。

  列表和惰性

  列表操作是函數編程的核心。FC++定義了自己的列表數據類型,它不同于 Standard Template Library(STL)列表。FC++列表是惰性的。可以在 FC++中創建包含無限多元素的列表,但是只在需要時計算這些元素。清單 6 說明其意義。

清單 6.定義和使用惰性列表

 
List<int> numbers = enumFrom (33);
List
<int> even_and_greater_than_33 = filter (even, numbers);
assert (take(
4, even_and_greater_than_33)) = list_with (34, 36, 38, 40);

  enumFrom、filter、even、take 和 list_with 是 FC++中預定義的函數。在清單 6 中,enumFrom 返回一個從 33 開始的無限數字列表。filter 例程返回另一個無限列表,其中的數字是大于 33 的偶數。最后,take 例程實際上從這個列表中提取出前 4 個元素。顯然,列表實際上不會存儲無限多的數字—只在需要時計算元素。表 1 說明 FC++列表常用的一些函數。

表 1.與 FC++列表結合使用的函數

函數 說明
head(<list>) 返回列表的第一個元素
tail(<list>) 返回一個列表,其中的元素與 <list> 相同,但是不包含第一個元素
cons(<element >,<list>) 返回一個列表,其中的元素與 <list> 相同,但是在最前面添加了<element>
NIL 表示空列表
list_with(<element1, element2>,…,<elementN>) 創建一個包含 N 個元素的列表
enumFrom(<element1>) 創建一個從 element1 開始的無限列表
compose(<func1>,<func2>) compose (f, g)相當于 f(g(x)),其中的 f(x)和 g(x)是兩個函數
filter(<func1>,<list>) 返回使用 <func1> 函數篩選 <list> 所獲得的元素列表
take(<N>,<list>) 返回包含 <list> 中前 N 個元素的列表
map(<function>,<list>) 把第一個 <function> 函數應用于第一個 <list> 的每個元素

  清單 7 中的示例說明如何創建和顯示列表的內容。

清單 7.創建列表、檢查列表內容并顯示數據

 
#include <iostream>
#include "prelude.h"

int main()
{

int x=1,y=2,z=3;
List
<int> li = cons(x,cons(y,cons(z,NIL)));

// head also removes the 1st element from the list
assert( head(li) == 1 );

// tail returns whatever is left of in the list, and list_with is
// used to define small sized list
assert( tail(li) == list_with(2,3));

while( li ){
std::cout
<< li.head()<< "";
li
= li.tail();
}

return 0;
}

  注意:在創建 li 列表時,cons 在一個列表的前面添加元素;通過依次添加z、y和x創建最終的列表。

  更快的列表實現

  FC++ 1.5 提供 List 數據結構的另一種變體,它稱為 OddList,在 list.h 中定義。OddList 的接口與 List 完全相同,但是它更快。所有對 List 進行操作的 FC++例程也適用于 OddList。OddList 的效率高是由于它緩存列表中的下一個節點。清單 8 演示 OddList 的一些比較微妙的用法。

清單 8.比較微妙的 OddList 用法

 
OddList<int> odd1 = enumFrom (1);
List
<int> list1 = odd1.tail (); // always returns List<int>!!

OddList<int> odd2 = enumFrom (1);
List
<int> list2 = odd2.delay(); // create a List<int> with same data as odd2

List<int> list3 = enumFrom (1);
OddList
<int> odd3 = list3.force (); // creates an OddList<int> with same data as list3

  OddList 不支持適用于 List 的 STL 式迭代器。關于 OddList 實現的詳細信息見參考資料。

  創建自己的篩選器

  如果希望在清單 6 中創建自己的篩選器(例如所有能夠被 100 整除的大于 33 的數字),那么只需定義自己的篩選函數,然后調用 ptr_to_fun 把它轉換為函數對象。清單 9 說明具體做法。

清單 9.創建自己的篩選器

 
bool div_by_100 (int n){
return n % 100 ? false : true;
}

List
<int> num = enumFrom(34);
List
<int> my_nums = filter( ptr_to_fun(&div_by_100), num);

  注意,FC++ List 和 filter 在本質上是完全泛化的,可以適用于任何數據類型。接下來,討論兩種基本的函數技術:currying 和合成。
  currying

  currying 這種函數編程技術把某一函數的一部分參數綁定到固定的值,由此創建新函數。清單 10 中的示例對 f 函數執行 currying。

清單 10.使用 currying 創建新函數

 
int multiply(int m, int n){ return m * n; }
Fun2
<int, int, int> f2 = ptr_to_fun (&multiply);
Fun1
<int, int> f1 = curry2 (f2, 9);

std::cout
<< f1(4)<< std::endl; // equivalent to multiply(9, 4)

Fun1<int, int> f1_implicit = f2(9);
std::cout
<< f1_implicit(4)<< std::endl; // same as f1(4)

  預定義的 curry2 例程把 f2 的第一個參數綁定到 9。FC++ 1.5 提供 curry1、curry2 和 curry3 操作符,它們把前 N 個參數綁定到特定的值。另外,FC++還定義綁定例程,它們通過預先固定現有函數的特定參數的值來創建新函數。例如,bind2and3of3 (f, 8, 9)相當于 f(x, 8, 9),其中的 f(x,y,z)是有 3 個輸入參數的函數。另一種對參數進行特化的方式是使用下劃線(_)。例如,greater (_, 10)相當于 f(x) = (x> 10)。注意,greater 是 FC++中預定義的。清單 11 給出一些 currying 示例。

清單 11.更多 currying 示例

 
List<int> integers = enumFrom (1);
List
<int> int_gt_100 = filter(greater(_, 100), integers);

// This list will add 3 to all elements of integers.
List<int> plus_3 = map (plus(3), integers);

  清單 12 中的代碼片段顯示一個數字的所有因子,包括這個數字本身。

清單 12.顯示一個數字的所有因子

 

#include "prelude.h"
using namespace fcpp;

#include
<iostream>
using namespace std;

bool divisible( int x, int y){ return x%y==0; }

struct Factors : public CFunType<int,OddList<int> > {
OddList
<int> operator()( int x) const {
return filter( curry2(ptr_to_fun(&divisible),x), enumFromTo(1,x));
}
} factors;


int main()
{
OddList
<int> odd = factors(20);
while (odd){
cout
<< head(odd)<< endl;
odd
= tail(odd);
}

return 0;
}

  理解清單 12 的關鍵是這一行:return filter( curry2(divisible,x), enumFromTo(1,x));。這為 enumFrom(1, 20)返回的列表創建一個篩選器,讓 20 能夠整除的所有數字成為最終列表的一部分。curry2 例程把 20 綁定到 divisible 函數的第一個參數。注意,ptr_to_fun 把 divisible 轉換為函數對象,這樣就可以把它作為參數傳遞給 curry2。

  合成

  函數編程通過組合現有代碼創建新功能。compose ()操作符把兩個一元函數 f(x)和 g(x)組合成新函數 h(x),h(x)相當于 f(g(x))。例如,compose (head, tail)返回列表中的第二個元素。這是真正意義上的函數編程;g(x)作為 f(x)的參數。取自"Functional Programming with the FC++ Library"的清單 13(見參考資料)是一個使用合成的示例。

清單 13.使用 compose 和 tail 獲取列表的第二個元素

 
std::string s="foo", t="bar", u="qux";
List
<std::string> ls = cons(s, cons(t, cons(u, NIL)));

ls
= compose(tail, tail)(ls); // tail(tail(ls));
assert (head(ls) == "qux"); // s, t are removed

  清單 14 中的示例讓列表的所有元素遞增 2。

清單 14.使用 compose 遞增列表元素      

 
List<int> integers = enumFrom (1);
map (compose(inc, inc), integers);

// this modifies integers to an infinite list [3, 4, 5 ...]

  lambda 函數

  討論函數編程就不能不提到 lambda 函數。lambda 抽象用來定義匿名函數。在不希望為小代碼段定義單獨的函數的情況下,可以使用這種技術。要想在代碼中使用 lambda 功能,需要定義 FCPP_ENABLE_LAMBDA 宏。清單 15 從現有代碼簡便地定義新的數學和邏輯函數。請注意定義 factorial 的方式。

清單 15.定義 lambda 函數

 
// a new function where f(x) = 3*x+1
lambda(X)[ plus[multiplies[3,X],1] ]

// a new function where f(x) = x!(factorial x)
lambda(X)[ l_if[equal[X,0],1,multiplies[X,SELF[minus[X,1]]]] ]

  清單 15 中的代碼的意義很明確。plus、multiplies 等例程是在 FC++庫中定義的,使用 lambda 操作符從現有代碼創建新功能。

  結束語

  FC++提供:CFunType 類型的對象,可以通過擴展它輕松地滿足函數編程的需要惰性列表的實現,其中可以容納數量無限的元素 head、tail、map、filter、ptr_to_fun 等函數編程操作符。使用 currying 操作符、lambda 或 compose 從現有函數創建新函數的能力。FC++惟一的缺點可能是缺少描述頭文件中定義的函數的標準化文檔。本文介紹了最有用的一些函數:compose、curry、bind、take、map、ptr_to_fun 和 filter。

0
0
 
 
 

文章列表

全站熱搜
創作者介紹
創作者 大師兄 的頭像
大師兄

IT工程師數位筆記本

大師兄 發表在 痞客邦 留言(0) 人氣()