この記事は機械翻訳されたものです。

作業プログラマ

マルチパラダイムと .NET (第 9 部): 関数型プログラミング (機械翻訳)

Ted Neward

Ted Neward記事のシリーズを取得 2 桁近くに、いつでも 2 つのことが起こっている: かの著者は十分に彼の読者が実際には、件名行でその回数に興味を持っているか彼の新しいトピックに考えるにはあまりにも間抜けなことだと思うに大げさです。または、私は思います、時々、件名だけはあまり報道メリットします。 ここの場合は関係なく、我々 のホーム ストレッチを今している安心します。

前の部分は、6 月に (msdn.microsoft.com/magazine/hh205754)、名前ベースの軸に沿って変動を提供するという名前付け規則と動的プログラミングを使用して、顕微鏡下で発想 — は、名前では、バインディングで。ネット通常いくつかのレベルでの反射を意味 — いくつか興味深い設計上の問題を解決するために。 ほとんど。純開発者、私は想像すると、彼らが発生する動的なプログラミングのほとんどは C # 4 を提供「動的」キーワードを使用が期待されます。 古い手 Visual Basic 開発者、しかし、知っている、Visual Basic のプログラマがそれを知っているに対し c# のみその活力によって最近、来た- と、多くの場合非常に成功した使用 — 数十年。

パラダイムの最後ではありません — 1 つ以上を探索するのには、あって、もう一度、1 つは、隠れている明白な一見にいくつかの年の間に今は。 デザイン アルゴリズム軸上の共通性変動と機能的なデザインを記述するためには確かに簡単な (生意気では少しの場合) ですが、この oversimplifies し、同時にその機能を隠します。

1 つの文で関数型プログラミング治療機能について、ちょうど私たちすることができます私たちの周りの関数を渡すことができます意味は他のデータ値型などの値としてデータ値でなくこれらの値から新しい値を派生します。 またはより正確に言えば、関数を言語のファーストクラスの市民として扱われる必要があります: 作成、メソッドに渡される、できます他の値は、メソッドから返されました。 説明、また、正確に啓発していませんが、単純な事例での開始をので。

デザイン運動は小さなコマンドライン計算機を作成する想像する: ユーザー型 (パイプ)、数式と計算機解析結果を印刷します。 この設計は非常に簡単に示すように図 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));
    }
  }
}

書かれて動作-計算機枢機卿の 4 つの演算子以外のものを受け取るまで。 何悪いです、しかし、(プログラムの全体的なサイズに比較して) コードのかなりの量の重複するコードです、私たち新しい数学的な操作をシステムに追加すると重複するコードにしていきます (など、演算子、%、または、指数演算子モジュロ ^)。

ちょっとバック ステップ、それは明らかに、実際の操作-2 つの数値に何をされている-何ここでは、によって異なりますが、それがこれより一般的な形式に示すように書き換えることができますすることができれば図 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)
    {
      // ...
}
  }

明らかに、私たち単にスイッチ/場合ブロック操作を作り直すことが、本当に多くを得る doesn't。 理想的には、いくつかの種類の文字列の操作参照ご (、表面には動的フォーム プログラミング再度、名前のバインドには + を加法操作、たとえば)。

デザイン パターン世界の中で、このケース、戦略パターンは、基本クラスまたはインターフェイスの必要な署名とコンパイル時の typechecking の線に沿って何かの安全性を提供する、コンクリートのサブクラスを実装は。

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

… の作品のよう。 それは各操作を作成する新しいクラスを必要とするかなり詳細になります。 それも非常にオブジェクト指向でないので本当に必要がある 1 つのインスタンスのみは、これまで、参照テーブルと一致する、実行中のホストします。

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

この簡素化することができますそれは何とか気; そして、何人かの読者はおそらくすでに実現しているように、これは前に 1 回、すでに解決されている問題以外のイベント ハンドラー コールバックのコンテキストで。 これは正確には、デリゲートはコンストラクトが作成された 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; }
}

精通した c# 3 プログラマはすぐに認識これも、さらに短縮することができることを使用してラムダ式、その言語のバージョンで導入されました。 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);
}

ここにいくつかのマイナーなバグ (ほとんどコード ビール私の 11 歳の息子に渡していることには)、このコードの最大の問題は? それは本質的に un-reusable です。 誰も別のビールを提供しようと、don't を繰り返して自分 (ドライ) の原則に違反する別の foreach ループが必要です。 我々 はもちろん、メソッド (古典的な手続き型共通応答) にビールを発行するコードを集める可能性がある、ましょう。

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

(私は以上年齢チェックを追加することがわかります。 私の妻は、シャーロット、この資料の文書に行くことができる前に私は含まれて主張。)欲望は、16 歳以上の誰もが検索して、無料の R-投稿日時: 映画のチケット、代わりにするなら何か。 または 39 歳以上である誰を見つけ、それらを与えるには「聖牛古いしている !」バルーンですか? または誰もが 65 歳以上検索してそれぞれの事は (の名前、年齢、アドレス… のような) を忘れることがそう書いて小型ノートか? または最後の名前を誰もが」以外のフォード「見つけて、ハロウィーン パーティーに招待ですか?

多くよりクリアになります私たちを投げる、これらの例は、これらの各ケース変動の 2 つの要素を提示: 人物オブジェクト、およびこれらの各ユーザー オブジェクトで実行するアクションのフィルタ リングします。 デリゲート (と <T> の力を与え 述語 <T> 導入の種類。作成できること共通性が必要な変動を公開中に示すように網 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));
}

1 つの一般的な操作だ」に変換するには」(または、それについてより正確に、「プロジェクト」)、最後の名前、Person オブジェクト リストから文字列リストを抽出する場合など、別の型にオブジェクト (を参照してください図 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);
}

フィルター、実行、変換 (より多くの共通性・変動 !) の宣言のジェネリック使用するおかげで発見の最後の名前の表示を実行が再利用できることに注意してください。 予告も、どのようにラムダ式の使用を明確に開始読み込んだり — 1 つはまだ別の一般的な機能の操作を記述する場合は、さらにもっと明らかになる削減は、「コレクションを単一の値にすべての値が指定した方法で結合によって崩壊」. たとえば、私たちみんなの 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));
}

この削減操作多くの場合は、「折り、「ところで呼ばれます。 (機能の肥えたプログラマは、2 つの用語が若干異なりますが違いの主な議論に重要ではありません)。そして、はい、これらの操作は本当に何も何を 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));
}

作業企業に。NET 開発者は、この愚かなようです。 それはようではない実際プログラマーの時間を過ごす歳総和コードを再利用する方法を探してします。 本物のプログラマは、各 1 つの最初の名前を連結する文字列、使用、OData 要求または何かに適しての内部の XML 表現には、オブジェクトのコレクションを反復処理コードを記述します。

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

オエーッと吐きます。 LINQ オブジェクトのものがすべての後に役に立つかもしれないことを推測します。

機能ですか?

オブジェクト指向の古典的訓練を受けた開発者なら、これはばかげていると同時にエレガント。 このアプローチは文字通りソフトウェアの設計になるのでそれを吹くの瞬間をすることができますは、ほぼ正反対の方法オブジェクトから: システムでは、「もの」中心に、何かはそれら事それぞれに関連付けられている動作を作るよりも、機能のプログラミング システムの「動詞」を識別し、、どのように彼ら別種類データを操作できるかを参照してくださいにそうです。 どちらのアプローチは、他のより多くの権利です — それぞれの共通性をキャプチャし、非常に異なる軸変動を提供しています、想像することがありますように、場所はそれぞれエレガントでシンプルなと各醜いと不器用なをすることができます。

覚えて、古典的なオブジェクト指向の可変性構造のレベルでは、フィールドとメソッドの追加または置換 (上書き) による既存のメソッドが、何もアドホック アルゴリズムの動作をキャプチャして肯定的な変化を作成する機能を提供しています。 実際には、それまでではなかった。ネットの共通性・可変性の軸が可能になったと匿名メソッドを得た。 C# 1.0 では、このような何かを行うことが可能だったが各ラムダどこか、宣言されている、名前付きメソッドと各メソッドが c# 1.0 がパラメーター化された型があるなかったので (これはそれらのメソッド内のキャストを意味) System.Object 用語では、入力するならなかった。

関数型言語の長年の実践は関数型言語の値としてだけ渡す関数の周り以外を行うことができます他の多くのことがあるので、実際に私はここの記事を終了することでうんざり — 関数の部分適用は関数型プログラミングの多くの非常にタイトなエレガントなそれを直接サポートする言語で作る巨大な概念 — しかし、編集のニーズを満たす必要があります、とは私は私の距離の制限をプッシュです。 ちょうどこのファンクショナル ・ アプローチの多くを見て (と機能 LINQ で既に存在で武装した) いくつか強力な新しいデザインの洞察力を提供することができます。

もっと重要なは、これのより多くを見ることに関心のある開発者は、長い、ハード F #、見てください、すべての。言語ネット、これらの機能の概念 (アプリケーションの一部と currying) を言語のファーストクラスの市民としてキャプチャ 1 つだけです。 C# および Visual Basic の開発者ことができますのようなことが、いくつかのライブラリの支援 (新しい型およびメソッド F # 自然は何を行うには) 必要とします。 幸いにも、いくつかそのような努力は進行中、「機能 c#」ライブラリは、CodePlex で利用可能なを含むです (functionalcsharp.codeplex.com)。

様々 なパラダイム

それのようにまたはないなマルチパラダイム インターの言語に共通の使用、ここに滞在しているように見えます。 Visual Studio 2010 言語各ある程度、様々 なパラダイムの各展示; C++ などがある C++ コンパイラで動作し、方法のために、マネージ コードで行うことができないいくつかパラメトリック メタプログラミング設備とは、最近 (最新の c + + 0 x 標準) ラムダ式を得た。 多くの非難を浴びたの ECMAScript、JavaScript、JScript 言語もは、オブジェクト、プロシージャ、メタプログラミング、動的、機能のパラダイムを行うことができます; 実際には、JQuery の多くのこれらのアイデアに組み込まれています。

コーディングを楽しんで !

Ted Neward Neward のプリンシパルである & 、エンタープライズに特化した、独立した会社を関連付けます。ネット フレームワークおよび Java プラットフォームのシステム。これまでに 100 個を超える記事を執筆している Ted は、C# MVP であり、INETA の講演者でもあります。さまざまな書籍を執筆および共同執筆していて、『Professional F# 2.0』(Wrox 2010 年、英語) もその 1 つです。彼は定期的にコンサルティングを行い、開発者を指導しています。彼の連絡先は ted@tedneward.com (英語のみ) です。彼がチームの作業に加わることに興味を持ったり、ブログをご覧になったりする場合は、 blogs.tedneward.com (英語) にアクセスしてください。

この記事のレビュー、技術スタッフに感謝:マシュー Podwysocki