本文章是由機器翻譯。

工作的程式師

多範型 .NET,第 9 部分:函式程式設計

.Java

兩件事之一發生了一系列文章獲取靠近雙位數,任何時間: 要麼作者是夠狂妄,認為他的讀者實際上感興趣的主題很多次在行中,或他是太傻蛋想出一個新的主題。或者,我猜想,有時主題只是值得這麼多的覆蓋範圍。無論是在此情況下,放心我們現在家裡一片。

在六月號上的一段 (msdn.microsoft.com/magazine/hh205754),提供基於名稱的軸的變異性的想法在顯微鏡下,使用的命名約定和動態規劃 — — 就綁定的名稱,在其中。網路通常意味著某些層面的思考 — — 以解決一些有趣的設計問題。大多數。淨的開發人員,我想像,希望他們遇到的動態規劃的大部分將通過 C# 4 提供的"動態"關鍵字。舊手 Visual Basic 開發人員都知道,但是,C# 只是由其活力最近,而 Visual Basic 程式師知道它 — — 和相當成功地使用它,在許多情況下 — — 幾十年。

但這不是最後的范式 — — 之一更仍有待探討,並再次,這是一個被隱藏于眼前幾年了。而容易當然 (如果稍微冒失) 形容為通用性變異演算法軸設計的功能設計,這同時便含糊其辭,掩蓋了它的能力。

一句話,功能程式設計是有關處理函數值,就像任何其他資料數值型別,意味著我們可以通過周圍的功能,就像我們可以作為資料的值,以及派生出這些值的新值。或者,更準確地說,功能應視為語言中的一流公民: 他們可以創建、 傳遞給方法和方法返回正如其他值。但是,解釋,再次,不正是啟發,讓我們開始簡單的案例研究。

想像設計工作是創建一個小的命令列計算器: 使用者類型 (或管道) 中,一個數學運算式和計算器進行分析和列印結果。設計這是非常簡單,如圖所示,在圖 1

圖 1一個簡單的計算器

class Program
{
  static void Main(string[] args)
  {
    if (args.Length < 3)
        throw new Exception("Must have at least three command-line arguments");

    int lhs = Int32.Parse(args[0]);
    string op = args[1];
    int rhs = Int32.Parse(args[2]);
    switch (op)
    {
      case "+": Console.WriteLine("{0}", lhs + rhs); break;
      case "-": Console.WriteLine("{0}", lhs - rhs); break;
      case "*": Console.WriteLine("{0}", lhs * rhs); break;
      case "/": Console.WriteLine("{0}", lhs / rhs); break;
      default:
        throw new Exception(String.Format("Unrecognized operator: {0}", op));
    }
  }
}

如寫,它的工作原理 — — 直到計算器收到基數四營辦商以外的其他內容。 什麼更糟的是,雖然,大量的代碼 (相比,程式的整體大小) 是重複的代碼,並將繼續是重複的代碼,因為我們向系統添加新的數學運算 (如模運算子、 %或指數的運算子,^)。

步進了一會兒後,很明顯,實際操作 — — 這兩個數字正在採取哪些措施 — — 是什麼而異,並最好能寫這更通用的格式,如中所示圖 2

圖 2更通用的計算器

class Program
  {
    static void Main(string[] args)
    {
      if (args.Length < 3)
          throw new Exception("Must have at least three command-line arguments");

      int lhs = Int32.Parse(args[0]);
      string op = args[1];
      int rhs = Int32.Parse(args[2]);
      Console.WriteLine("{0}", Operate(lhs, op, rhs));
    }
    static int Operate(int lhs, string op, int rhs)
    {
      // ...
}
  }

很明顯,我們可能只是重新創建的開關/箱塊的操作,但這真的不會有很大益處。 理想情況下,我們想要的字串操作查找某種 (其中,表面上看,一種形式的動態程式設計再次,綁定名稱"+"添加劑的操作,例如)。

內設計模式世界中,這將是一例戰略模式,在具體的子類實現的基類或介面,為安全起見,東西沿線的提供所需的簽名和編譯時拼寫檢查:

interface ICalcOp
{
  int Execute(int lhs, int rhs);
}
class AddOp : ICalcOp { int Execute(int lhs, int rhs) { return lhs + rhs; } }

其中工程 … …。 它是很詳細,需要為每個操作,我們想要創建一個新類。 也不是很物件導向,因為我們真的只需要一個它的實例,過,宿主內的匹配和執行的查閱資料表:

private static Dictionary<string, ICalcOp> Operations;
static int Operate(int lhs, string op, int rhs)
{
  ICalcOp oper = Operations[op];
  return oper.Execute(lhs, rhs);
}

不知怎麼感覺像這可以簡化 ; 有些讀者可能已經認識到,這是一次之前,已經解決了的問題,並在事件處理常式回檔的情況下除外。 這是完全委託構造在 C# 中為創建:

delegate int CalcOp(int lhs, int rhs);
static Dictionary<string, CalcOp> Operations = 
  new Dictionary<string, CalcOp>();
static int Operate(int lhs, string op, int rhs)
{
  CalcOp oper = Operations[op];
  return oper(lhs, rhs);
}

而且,當然,操作人員必須用計算器承認,但添加新的變得有點容易操作正確初始化:

static Program()
{
  Operations["+"] = delegate(int lhs, int rhs) { return lhs + rhs; }
}

精明 3 C# 程式師都會立刻意識到可以更進一步,縮短使用lambda 運算式,其仲介紹了該語言的版本。 Visual Basic 在 Visual Studio 2010,可以做類似:

static Program()
{
  Operations["+"] = (int lhs, int rhs) => lhs + rhs;
}

這是代表和一般的大多數 C# 和 Visual Basic 開發思路停止的位置。 但也不和委託是有趣得多,尤其是當我們開始更進一步擴展。 這個想法,周圍的傳遞函數並使用它們,在各方面的深入。

減少、 映射和折疊 — — 哦我 !

通過各地的函數不是我們習慣于在主流的東西。淨的發展,所以這可以怎樣利用設計的一個更具體的例子是必要。

假設我們有人物件的集合,如中所示的一下圖 3

圖 3人物件的集合

class Person
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public int Age { get; set; }
}

class Program
{
  static void Main(string[] args)
  {
    List<Person> people = new List<Person>()
    {
      new Person() { FirstName = "Ted", LastName = "Neward", Age = 40 },
      new Person() { FirstName = "Charlotte", LastName = "Neward", Age = 39 },
      new Person() { FirstName = "Michael", LastName = "Neward", Age = 17 },
      new Person() { FirstName = "Matthew", LastName = "Neward", Age = 11 },
      new Person() { FirstName = "Neal", LastName = "Ford", Age = 43 },
      new Person() { FirstName = "Candy", LastName = "Ford", Age = 39 }
    };
  }
}

現在,確實是這樣管理要慶祝的東西 (或許他們都取得配額)。 他們想要做什麼是給這些人,每一杯啤酒,很容易完成使用傳統的 foreach 迴圈中,如中所示的圖 4

圖 4傳統 foreach 迴圈

static void Main(string[] args)
{
  List<Person> people = new List<Person>()
  {
    new Person() { FirstName = "Ted", LastName = "Neward", Age = 40 },
    new Person() { FirstName = "Charlotte", LastName = "Neward", Age = 39 },
    new Person() { FirstName = "Michael", LastName = "Neward", Age = 17 },
    new Person() { FirstName = "Matthew", LastName = "Neward", Age = 11 },
    new Person() { FirstName = "Neal", LastName = "Ford", Age = 43 },
    new Person() { FirstName = "Candy", LastName = "Ford", Age = 39 }
  };
  foreach (var p in people)
    Console.WriteLine("Have a beer, {0}!", p.FirstName);
}

有一些小的 bug (大多是在您的代碼啤酒交給我 11 歲的兒子),但此代碼的最大問題嗎? 這是本質上 un-reusable。 嘗試後給大家另一個啤酒要求另一個 foreach 迴圈,這是違反不重複自己 (幹) 原則。 我們當然可以,到一種方法 (經典程式共同回應),收集啤酒發行代碼如下所示:

static void GiveBeer(List<Person> people)
{
  foreach (var p in people)
    if (p.Age >= 21)
        Console.WriteLine("Have a beer, {0}!", p.FirstName);
}

(請注意我添加過 21 歲檢查 ; 我的妻子,夏洛特,堅持我包括它之前這篇文章可以去出版物。)但如果願望是找到每一個 16 歲以上的人,而是給他們張免費的 R 級電影嗎? 或以找到所有人是 39 歲以上,給他們"聖牛你是舊 !"氣球? 或發現大家都 65 歲以上,給他們每個人一個小筆記本,把事情記他們很有可能忘記 (像他們的名字、 年齡、 位址 … …) 嗎? 或發現大家都具有姓氏以外"福特",並邀請他們參加萬聖節派對嗎?

更多我們折騰過,愈看得清楚這些示例的每種情況下提出了兩個元素的變化: 篩選人物件,並要與每個這些人物件執行的操作。 鑒於代表 (和行動 <T> 電源 和謂詞 <T> 在推出的類型。NET 2.0),如中所示,我們可以在時仍公開必要的變異性,創建通用性圖 5

圖 5過濾人物件

static List<T> Filter<T>(List<T> src, Predicate<T> criteria)
{
  List<T> results = new List<T>();
  foreach (var it in src)
    if (criteria(it))
      results.Add(it);
  return results;
}
static void Execute<T>(List<T> src, Action<T> action)
{
  foreach (var it in src)
    action(it);
}
static void GiveBeer(List<Person> people)
{
  var drinkers = Filter(people, (Person p) => p.Age >= 21);
  Execute(drinkers, 
      (Person p) => Console.WriteLine("Have a beer, {0}!", p.FirstName));
}

一個更為常見操作是,"變換"(或者,若要將這件事更準確,"專案") 到另一種類型,例如,當我們想要從人的物件清單中最後一個名稱解壓的字串清單物件 (請參見圖 6)。

圖 6從清單的字串清單物件轉換

public delegate T2 TransformProc<T1,T2>(T1 obj);
static List<T2> Transform<T1, T2>(List<T1> src, 
  TransformProc<T1, T2> transformer)
{
  List<T2> results = new List<T2>();
  foreach (var it in src)
    results.Add(transformer(it));
  return results;
}
static void Main(string[] args)
{
  List<Person> people = // ...
List<string> lastnames = Transform(people, (Person p) => p.LastName);
  Execute(lastnames, (s) => Console.WriteLine("Hey, we found a {0}!", s);
}

請注意,多虧了泛型使用篩選器、 執行和變換 (更多公共/變異性 !) 的聲明中,我們可以重用執行顯示每個找到的最後一個名稱。 通知,太,如何使用 lambda 運算式使有趣的暗示,開始來明確 — — 之一,當我們編寫另一個常見的功能操作變得更明顯減少,其中"折疊"集合下到單個值所指定的方式相結合的所有值。 例如,我們可以在其中添加了每個人的年齡來檢索使用 foreach 迴圈的總和的全年齡值如下所示:

int seed = 0;
foreach (var p in people)
  seed = seed + p.Age;
Console.WriteLine("Total sum of everybody's ages is {0}", seed);

中所示,也可以編寫它使用泛型的減少,圖 7

圖 7使用一個通用的減少

public delegate T2 Reducer<T1,T2>(T2 accumulator, T1 obj);
static T2 Reduce<T1,T2>(T2 seed, List<T1> src, Reducer<T1,T2> reducer)
{
  foreach (var it in src)
    seed = reducer(seed, it);
  return seed;
}
static void Main(string[] args)
{
  List<Person> people = // ...
Console.WriteLine("Total sum of everybody's ages is {0}", 
    Reduce(0, people, (int current, Person p) => current + p.Age));
}

此減少操作通常稱為"折疊",順便。 (明眼人功能程式師,這兩個詞都略有不同,但差別並不是討論的主要問題的關鍵。)是的如果你已經開始懷疑這些行動是真的只不過 LINQ 提供的物件 (愛太少,當它最初發佈的 LINQ 物件功能),您會準確 (請參見圖 8)。

圖 8折疊操作

static void Main(string[] args)
{
  List<Person> people = new List<Person>()
  {
    new Person() { FirstName = "Ted", LastName = "Neward", Age = 40 },
    new Person() { FirstName = "Charlotte", LastName = "Neward", Age = 39 },
    new Person() { FirstName = "Michael", LastName = "Neward", Age = 17 },
    new Person() { FirstName = "Matthew", LastName = "Neward", Age = 11 },
    new Person() { FirstName = "Neal", LastName = "Ford", Age = 43 },
    new Person() { FirstName = "Candy", LastName = "Ford", Age = 39 }
  };
  // Filter and hand out beer:
  foreach (var p in people.Where((Person p) => p.Age >= 21))
    Console.WriteLine("Have a beer, {0}!", p.FirstName);

  // Print out each last name:
  foreach (var s in people.Select((Person p) => p.LastName))
    Console.WriteLine("Hey, we found a {0}!", s);

  // Get the sum of ages:
  Console.WriteLine("Total sum of everybody's ages is {0}", 
    people.Aggregate(0, (int current, Person p) => current + p.Age));
}

工作企業。網路開發人員,這看起來愚蠢。 不是像真正程式師花時間尋找重用年齡求和代碼的方法。 真正的程式師編寫代碼,迴圈訪問物件的集合,每個名字串聯成字串,適用于 OData 請求或東西裡面的 XML 表示形式:

Console.WriteLine("XML: {0}", people.Aggregate("<people>", 
  (string current, Person p) => 
    current + "<person>" + p.FirstName + "</person>") 
  + "</people>");

低下高貴的頭顱。 猜 LINQ 物件的東西畢竟可能很有用。

更多的功能嗎?

如果你是經典受過訓練的物件導向的開發人員,這看起來可笑同時又優雅。 它可以是一個令人興奮的時刻,因為這種方法確實是在軟體設計中近截然相反的物件的方式: 重點放在"東西"在系統中,並使行為附加到每個這些東西的東西,而不是函數程式設計看上去識別系統中的"動詞",看看他們如何可以對不同類型的資料操作。 兩種方法是比其他更合適 — — 每個捕獲的共同性和提供了非常不同的軸線,沿變異性和可能想像的有的地方每在哪裡優雅而簡單,和其中每個可以是醜陋又笨拙。

請記住,在經典的物件定位變異是一級結構,提供創建通過添加欄位和方法或替換現有的方法 (通過重寫),但沒有捕獲特設演算法行為的積極變化的能力。 事實上,直到。淨了匿名方法此軸通用性/變異性變得可行。 很可能像下麵這樣做在 C# 1.0 中,但每個 lambda 不得不聲明某個地方的命名的方法,每種方法不得不 System.Object 條款 (這意味著這些方法中的向下轉換),在鍵入,因為 C# 1.0 沒有參數化的類型。

功能語言的長期從業人員會感到害怕這一事實我結束這篇文章在這裡,因為有許多其他功能的語言可以的做除了只傳遞函數周圍為值 — — 部分應用程式的功能是一個巨大的概念,使很多函數程式設計非常緊和優雅,直接支援的語言 — — 但必須滿足編輯的需要和我這樣推我長度限制,因為它是。 即便如此,看到只是這麼多的功能的方法 (和武裝與功能已經存在於 LINQ) 可以提供一些功能強大的新設計洞察力。

更重要的是,開發人員希望看到更多的這應該看長、 硬 F #,其中的所有。網路語言,是作為一流公民語言中捕獲這些功能的概念 (部分應用程式和討好) 的唯一。 C# 和 Visual Basic 開發人員可以做類似的事,但需要一些圖書館援助 (新的類型和方法做 F # 什麼自然)。 幸運的是,幾個這種正在努力,包括"功能 C#"庫中,可用 CodePlex 上 (functionalcsharp.codeplex.com)。

各種范式

喜歡與否,multiparadigm 語言很常用,和他們看起來像他們會留下來。 每個 Visual Studio 2010 語言表現出某種程度的每個不同的模式 ; C + +,例如,有一些不可能在託管代碼中,由於對 c + + 編譯器操作的方法的參數化程式設計設施和最近剛 (在最新的 C + + 0 x 標準) 獲得了 lambda 運算式。 甚至飽受詬病 JScript/ECMAScript/JavaScript 語言可以做物件,程式,元程式設計、 動態和功能范式 ; 事實上,JQuery 的大部分是建基於這些想法。

編碼愉快 !

.Java 是首席 Neward & 將關聯,獨立的公司,專門從事企業。NET 框架和 Java 平臺系統。他寫了 100 多個文章,是 C# MVP 和 INETA 的揚聲器,和有創作或合著了十幾本書,其中包括"專業 F # 2.0"(Wrox,2010年)。他諮詢、 顧問服務定期 — — 達到他在ted@tedneward.com 如果你有興趣在他來處理您的團隊,或閱讀他的博客上有 blogs.tedneward.com

感謝至下列技術專家檢閱這份文件: Matthew Podwysocki