次の方法で共有


実践的なパターン

日常の .NET 開発のための関数型プログラミング

Jeremy Miller

この 3 ~ 4 年間の .NET エコシステムの中で最も重要な進化とは何でしょうか。Windows Communication Foundation (WCF) や Windows Presentation Foundation (WPF) といった、新しいテクノロジやフレームワークの名前が挙がるかもしれません。しかし、私個人にとっては、.NET Framework の最新の 2 リリースで C# と Visual Basic 言語に追加された強力な機能が、私の日々の開発作業に非常に大きな変化をもたらしていると言えるでしょう。今回のコラムでは、特に .NET 3.5 の関数型プログラミング技法向けの新たなサポートが、次の点でどのように役立つかを説明します。

  1. コードをより宣言型のコードにする
  2. コード内のエラーを減らす
  3. 多くの一般的作業を作成するコード量を少なくする

さまざまな同種の機能の中でも統合言語クエリ (LINQ) 機能は、.NET の関数型プログラミングの本領が発揮されたものといえますが、これは氷山の一角に過ぎません。

"日常の開発" というテーマに沿って話をすると、私はコード サンプルの大半を、基本的に C# 3.0 を使用し、JavaScript を多少織り交ぜながら作成しています。ただし、IronPython、IronRuby、F# など、その他の新しい CLR 対応プログラミング言語の方が、このコラムで紹介する関数型プログラミング技法をはるかに強力にサポートし、便利な構文を備えていることに注意してください。残念ながら、現バージョンの Visual Basic では、複数行のラムダ関数はサポートされないため、ここで説明する手法の多くを Visual Basic で同じように使用することはできません。しかし、Visual Basic 開発者の方は、Visual Studio 2010 でリリースされる次期バージョンの Visual Basic に備えて、これらの手法を検討されることを強くお勧めします。

ファーストクラスの関数

関数をメソッド間で受け渡したり、関数を変数として保持したり、関数を別のメソッドからの戻り値として返したりできる、ファーストクラスの関数が提供されるようになったため、関数型プログラミングの一部の要素を、C# や Visual Basic でも利用できるようになりました。C# と Visual Basic でファースト クラスの関数を実装するため、.NET 2.0 には匿名デリゲート、.NET 3.5 には新しいラムダ式があります。ただし、"ラムダ式" には、よりコンピューター サイエンス特有の意味があります。ファーストクラスの関数は別の一般用語として "ブロック" と呼ばれます。このコラムではこれ以降、ファーストクラスの関数の意味で "ブロック" という用語を使用します。"クロージャー" (この次に説明する、ある特定の種類のファーストクラス関数) や "ラムダ" は、思わぬ間違い (と本物の関数型プログラミングのエキスパートのお怒り) を避けるため使用しません。クロージャーには、クロージャー関数外部で定義される変数も含みます。JavaScript 開発用で人気上昇中の jQuery ライブラリを使用した経験をお持ちなら、おそらくクロージャーをかなり頻繁に使用されたでしょう。以下は、私の現在のプロジェクトから抜粋したクロージャーの使用例です。

// Expand/contract body functionality          
var expandLink = $(".expandLink", itemElement);
var hideLink = $(".hideLink", itemElement);
var body = $(".expandBody", itemElement);
body.hide();

// The click handler for expandLink will use the
// body, expandLink, and hideLink variables even
// though the declaring function will have long
// since gone out of scope.
expandLink.click(function() {
    body.toggle();
    expandLink.toggle();
    hideLink.toggle();
});

このコードは、<a> 要素をクリックすると、Web ページ上のコンテンツが表示または非表示になる、ごく一般的なアコーディオン効果を設定するために使用されます。クロージャー外で作成された変数を、この変数を使用するクロージャー関数に渡すことで、expandLink のクリック ハンドラーを定義しています。変数とクリック ハンドラーの両方を保持している関数は、ユーザーが expandLink をクリックするかなり前に終了しますが、クリック ハンドラーは body 変数と hideLink 変数を使用できます。

データとしてのラムダ

状況によっては、ラムダ構文を使用して、実行対象ではなく、データとして使用できる式をコード内で表現できます。特に私自身、この文を読んだ最初の何回かはその意味を理解できなかったので、実際に Fluent NHibernate ライブラリを使用して、明示的なオブジェクトとリレーショナル マッピングから取得されるデータとしてラムダを扱う例を見てみましょう。

 

public class AddressMap : DomainMap<Address>
    {
        public AddressMap()
        {
            Map(a => a.Address1);
            Map(a => a.Address2);
            Map(a => a.AddressType);
            Map(a => a.City);
            Map(a => a.TimeZone);
            Map(a => a.StateOrProvince);
            Map(a => a.Country);
            Map(a => a.PostalCode);
        }
    }

Fluent NHibernate が、式 a => a.Address1 を評価することはありません。Fluent NHibernate はこの式を解析して、基になる NHibernate マッピングで、使用する Address1 という名前を検索します。この手法は、.NET 分野の最近の多くのオープン ソース プロジェクトで広く使われるようになっています。PropertyInfo オブジェクトとプロパティ名を取得するためだけにラムダ式を使用することは、しばしば静的リフレクションと呼ばれます。

ブロックの受け渡し

関数型プログラミングを学習する最大の理由の 1 つは、ファーストクラス関数が、クラスよりも細かいコンポジション メカニズムを提供することで、コード内の重複を削減するしくみを理解することです。シーケンス中のどこか 1 ステップのみを除いて、基本の形式とまったく同じコード シーケンスに遭遇することがよくあります。オブジェクト指向プログラミングでは、テンプレート メソッド パターンと継承を使用することで、重複の解消を図ることができます。シーケンス中で他とは異なるステップを表すブロックを、基本シーケンスを実装する別のメソッドに渡すことで、このような重複を解消できます。私は近頃、これが優れた方法であるとますます実感するようになっています。

API を使いやすく、またエラーが起きにくくする最適な方法の 1 つは、繰り返し出現するコードを削減することです。たとえば、ADO.NET IDbConnection オブジェクトや、ステートフルまたは固定接続が必要なソケット リスナーなど、リモートのサービスやリソースにアクセスするための API という一般的な例について考えて見ましょう。通常、リソースを使用するには、接続を "開く" 必要があります。このようなステートフル接続では、リソースがかなり消費されるか不足することが多いため、作業が完了したらできるだけ早く接続を "閉じ" てリソースを解放し、他のプロセスやスレッドが使用できるようにすることが重要でした。

次のコードは、ある種のステートフル接続に対するゲートウェイを表すインターフェイスを示しています。

 

public interface IConnectedResource
    {
        void Open();
        void Close();

        // Some methods that manipulate the connected resource
        BigDataOfSomeKind FetchData(RequestObject request);
        void Update(UpdateObject update);
    }

別のクラスがこの IConnectedResource インターフェイスを使用する場合、他のメソッドを使用する前に、必ず Open メソッドが呼び出す必要があります。また、その後、Close メソッドを必ず呼び出す必要があります (図 1 参照)。

以前のコラムで、設計における内容と形式という対立する概念について説明しました (msdn.microsoft.com/magazine/dd419655.aspx を参照)。ConnectedSystemConsumer クラスが実行する必要がある機能の "内容" は、単に接続先リソースを使用して情報を更新することです。残念ながら、ConnectedSystemConsumer 内のほとんどのコードは、IConnectedResource との接続と切断およびエラー処理という "形式" に関わるものです。

図 1 IConnectedResource の使用

 

public class ConnectedSystemConsumer
{
private readonly IConnectedResource _resource;
public ConnectedSystemConsumer(IConnectedResource resource)
{
_resource = resource;
}
public void ManipulateConnectedResource()
{
try
{
// First, you have to open a connection
_resource.Open();
// Now, manipulate the connected system
_resource.Update(buildUpdateMessage());
}
finally
{
_resource.Close();
}
}
}

さらに悪いことには、"try/open/do stuff/finally/close" という形式のコードは、IConnectedResource インターフェイスを使用するたびに、重複して使わなければなりません。前述のとおり、設計を改善する最良の方法の 1 つは、コードに重複がある箇所では、必ず、重複を失くすようにすることです。ブロックまたはクロージャーを使用して、IConnectedResource API を使用する別の方法を試してみましょう。まず、インターフェイス分離の原則 (詳細については、objectmentor.com/resources/articles/isp.pdf を参照) を適用して、Open や Close のメソッドを使用しない、接続先リソースを呼び出すためだけのインターフェイスを抽出します。

 

public interface IResourceInvocation
    {
        BigDataOfSomeKind FetchData(RequestObject request);
        void Update(UpdateObject update);
    }

次に、IResourceInvocation インターフェイスで表される接続先リソースにアクセスするためだけに使用する 2 つ目のインターフェイスを作成します。

 

public interface IResource
    {
        void Invoke(Action<IResourceInvocation> action);
    }

では、新しい関数型 API を使用するように、ConnectedSystemConsumer クラスを書き直しましょう。

 

public class ConnectedSystemConsumer
    {
        private readonly IResource _resource;
 
        public ConnectedSystemConsumer(IResource resource)
        {
            _resource = resource;
        }

        public void ManipulateConnectedResource()
        {
            _resource.Invoke(x =>
            {
                x.Update(buildUpdateMessage());
            });
        }
    }

この新しいバージョンの ConnectedSystemConsumer では、接続リソースの設定や解放の方法を扱う必要がなくなっています。実際、ConnectedSystemConsumer は、IResource.Invoke メソッドにブロックまたはクロージャーを渡すことで、"最初に見つかった IResourceInvocation にアクセスして、次の指示を与える" ように IResource に指示しているだけです。前に不満を述べた "try/open/do stuff/finally/close" という繰り返し出現する形式コードはすべて、IResource という明確な実装になっています (図 2 参照)。

図 2 IResource の具体的な実装

 

frepublic
class Resource : IResource
{
public void Invoke(Action<IResourceInvocation> action)
{
IResourceInvocation invocation = null;
try
{
invocation = open();
// Perform the requested action
action(invocation);
}
finally
{
close(invocation);
}
}
private void close(IResourceInvocation invocation)
{
// close and teardown the invocation object
}
private IResourceInvocation open()
{
// acquire or open the connection
}
}

外部リソースへの接続を開いたり閉じたりする作業を、Resource クラスに含めることで、設計と API の使いやすさを改善できたと思います。また、アプリケーションの中核となるワークフローからインフラストラクチャ関連の問題の詳細を抜き出してカプセル化し、コードの構造も改善しました。ConnectedSystemConsumer の 2 番目のバージョンは、最初のバージョンに比べて、外部の接続先リソースの働きについて把握しておくべきことがかなり少なくなります。2 番目の設計では、システムが外部の接続先リソースと通信する方法をより簡単に変更できます。システムの中核をなすワークフローのコードを変更して、安定性が損なわれるリスクを冒す必要がありません。

2 番目の設計では、"try/open/finally/close" サイクルの重複を排除することで、システムにエラーが内在する可能性を軽減しています。コードを繰り返さなければならなくなるたびに、コーディングのミスを犯す可能性が生まれ、そのミスのために、技術的には正しく機能したとしても、リソースを使い果たし、アプリケーションのスケーラビリティを損なう可能性があります。

遅延実行

関数型プログラミングについて理解すべき最も重要な概念の 1 つは、遅延実行です。さいわい、この概念も比較的シンプルです。これは、インラインで定義したブロック関数を直ちに実行する必要はないというだけの意味です。では、遅延実行の実際の使用例を見てみましょう。

かなり大規模な WPF アプリケーションで、IStartable というマーカー インターフェイスを使用して、サービスがアプリケーションのブートストラップ プロセスの一環として起動される必要があることを示しています。

 

public interface IStartable
    {
        void Start();
    }

この特定のアプリケーションのすべてのサービスは、アプリケーションによって制御の反転 (Inversion of Control) コンテナー (この場合は StructureMap) から登録および取得されます。次の少量のコードでは、アプリケーションの起動時に、アプリケーション内で起動する必要があるすべてのサービスを動的に検出して、それらのサービスを起動しています。

 

// Find all the possible services in the main application
// IoC Container that implements an "IStartable" interface
List<IStartable> startables = container.Model.PluginTypes
    .Where(p => p.IsStartable())
    .Select(x => x.ToStartable()).ToList();
         

// Tell each "IStartable" to Start()
startables.Each(x => x.Start());

このコードには、3 つのラムダ式があります。たとえば、.NET 基本クラス ライブラリのすべてのソース コード コピーをこのコードにアタッチし、デバッガーを使用してこれをステップ スルーしようとしたとします。Where、Select、または Each 呼び出しにステップ インすると、ラムダ式は次に実行するコードではないこと、およびこれらのメソッドは container.Model.PluginTypes メンバーの内部構造を反復処理するために、すべてのラムダ式が複数回実行されることがわかると思います。遅延実行を考える別の方法として、Each メソッドを呼び出した場合を考えることができます。Each メソッドは IStartable オブジェクトが見つかるたびに実行する処理を指示しているにすぎません。

結果記憶型関数

"結果記憶型関数" (memoization) は、負荷の高い既存の関数呼び出しの実行を回避するために、同じ入力を使用している以前の実行結果を再利用する最適化手法です。私が初めて結果記憶型関数という用語に触れたのは、F# を使用した関数型プログラミングに関してでしたが、このコラムのための調査を進めるに従い、私が所属するチームが C# 開発で結果記憶型関数を頻繁に利用していることに気が付きました。たとえば、次のようなサービスを使用して、特定の地域のなんらかの計算済み金融データを頻繁に取得する必要があるとします。

 

public interface IFinancialDataService
    {
        FinancialData FetchData(string region);
    }

IFinancialDataService は、偶然にも非常に実行に時間がかかり、目的の金融データはかなり静的であるため、結果記憶型関数を利用すると、アプリケーションの応答がかなり改善されると思われます。内部の IFinancialDataService クラス用に結果記憶型関数を実装する IFinancialDataService のラッパー実装を作成できます (図 3 参照)。

図 3 内部 IFinancialDataService クラスの実装

 

public class MemoizedFinancialDataService : IFinancialDataService
{
private readonly Cache<string, FinancialData> _cache;
// Take in an "inner" IFinancialDataService object that actually
// fetches the financial data
public MemoizedFinancialDataService(IFinancialDataService
innerService)
{
_cache = new Cache<string, FinancialData>(region =>
innerService.FetchData(region));
}
public FinancialData FetchData(string region)
{
return _cache[region];
}
}

Cache<TKey, TValue> クラス自体は、Dictionary<TKey, TValue> オブジェクトのラッパーに過ぎません。図 4 は、この Cache クラスの一部を示しています。

図 4 Cache クラス

public class Cache<TKey, TValue> : IEnumerable<TValue> where TValue :
class
{
private readonly object _locker = new object();
private readonly IDictionary<TKey, TValue> _values;
private Func<TKey, TValue> _onMissing = delegate(TKey key)
{
string message = string.Format(
"Key '{0}' could not be found", key);
throw new KeyNotFoundException(message);
};
public Cache(Func<TKey, TValue> onMissing)
: this(new Dictionary<TKey, TValue>(), onMissing)
{
}
public Cache(IDictionary<TKey, TValue>
dictionary, Func<TKey, TValue>
onMissing)
: this(dictionary)
{
_onMissing = onMissing;
}
public TValue this[TKey key]
{
get
{
// Check first if the value for the requested key
// already exists
if (!_values.ContainsKey(key))
{
lock (_locker)
{
if (!_values.ContainsKey(key))
{
// If the value does not exist, use
// the Func<TKey, TValue> block
// specified in the constructor to
// fetch the value and put it into
// the underlying dictionary
TValue value = _onMissing(key);
_values.Add(key, value);
}
}
}
return _values[key];
}
}
}

Cache クラスの内容にご興味がある方は、StructureMap、StoryTeller、FubuMVC、そしておそらく Fluent NHibernate など、いくつかのオープン ソース ソフトウェア プロジェクトに実装されているバージョンを参考にできます。

Map/Reduce パターン

多くの一般的な開発作業は、関数型プログラミング手法を使用すると簡素化できることがわかっています。特に、コード内の list 操作や set 操作などは、"map/reduce" パターンをサポートする言語を使用すると、機械的に大幅に簡素化できます (LINQ では、 "map" は "Select" で "reduce" は "Aggregate" に相当します)。整数配列の合計を計算する方法について考えてみましょう。.NET 1.1 では、次のように配列を反復処理する必要がありました。

int[] numbers = new int[]{1,2,3,4,5};
int sum = 0;
for (int i = 0; i < numbers.Length; i++)
{
sum += numbers[i];
}

Console.WriteLine(sum);

.NET 3.5 で LINQ をサポートするための言語強化により、関数型プログラミング言語で一般的な map/reduce 機能が用意されました。現在では、上記のコードは簡単に次のように表すことができます。

int[] numbers = new int[]{1,2,3,4,5};
int sum = numbers.Aggregate((x, y) => x + y);

または、さらに簡単に、次のように表現することもできます。

int sum = numbers.Sum();
Console.WriteLine(sum);

Continuation (継続)

乱暴な言い方をすると、プログラミングにおける継続というのは、"次に何をするか" や "処理の残り" を表す抽象的な概念です。またあるときには、ユーザーが明示的に次の手順に進むことを許可したり、プロセス全体を取り消したりできるウィザード アプリケーションのように、コンピューター処理のプロセスの一部を完了することが重要な場合もあります。

では早速、コード サンプルを見てみましょう。WinForms または WPF を使用してデスクトップ アプリケーションを開発しているとします。なんらかの実行時間の長いプロセスを開始するか、応答が遅い外部サービスに画面操作からアクセスする必要が頻繁にあるとします。操作性の点から考えると、サービスの呼び出し中に、ユーザー インターフェイスをロックして応答できなくするようなことは当然避けたいので、サービス呼び出しはバックグラウンド スレッドで実行します。最終的にサービス呼び出しの結果が戻ってきたときに、サービスから返されたデータでユーザー インターフェイスを更新できますが、経験豊富な WinForms または WPF 開発者ならご存知のとおり、ここで更新できるのはメインのユーザー インターフェイス スレッドにあるユーザー インターフェイス要素だけです。

たしかに、System.ComponentModel 名前空間にある BackgroundWorker クラスを使用できますが、このインターフェイスによって表される CommandExecutor オブジェクトにラムダ式を渡す、別の方法を使用したいと思います。

public interface ICommandExecutor
    {
        // Execute an operation on a background thread that
        // does not update the user interface
        void Execute(Action action);

        // Execute an operation on a background thread and
        // update the user interface using the returned Action
        void Execute(Func<Action> function);
    }

最初のメソッドは、単に、バックグラウンド スレッドでアクティビティを実行するステートメントです。より興味深いのは、Func<Action> を受け取る 2 番目のメソッドです。通常、このメソッドがアプリケーション内でどのように使用されるかを見てみましょう。

まず、Model View Presenter (MVP: モデル ビュー プレゼンター) パターンの Supervising Controller (監視コントローラー) を使用して WinForms または WPF コードを作成しているとします (MVP パターンの詳細については、msdn.microsoft.com/magazine/cc188690.aspx を参照してください)。このモデルでは、Presenter クラスが、実行時間の長いサービス メソッドの呼び出しと、返されたデータを使用したビューの更新を管理します。新しい Presenter クラスは、単純に、以前紹介した ICommandExecutor インターフェイスを使用して、すべてのスレッド処理とスレッド マーシャリング作業を処理します (図 5 参照)。

図 5 Presenter クラス

public class Presenter
{
private readonly IView _view;
private readonly IService _service;
private readonly ICommandExecutor _executor;
public Presenter(IView view, IService service, ICommandExecutor
executor)
{
_view = view;
_service = service;
_executor = executor;
}
public void RefreshData()
{
_executor.Execute(() =>
{
var data = _service.FetchDataFromExtremelySlowServiceCall();
return () => _view.UpdateDisplay(data);
});
}
}

Presenter クラスは、別のブロックを返すブロックを渡すことで、ICommandExecutor.Execute を呼び出します。元のブロックは、実行時間の長いサービスの呼び出しを開始して、なんらかのデータを取得し、ユーザー インターフェイス (このシナリオでは IView) の更新に使用できる Continuation ブロックを返します。この例に限っては、更新情報をユーザー インターフェイス スレッドにマーシャリングし直す必要があるため、単に IView を同時に更新するのではなく、Continuation ブロックを返す方法を使用することが重要です。

図 6 は、ICommandExecutor インターフェイスの具体的な実装を示しています。

図 6 ICommandExecutor の具体的な実装

public class AsynchronousExecutor : ICommandExecutor
{
private readonly SynchronizationContext _synchronizationContext;
private readonly List<BackgroundWorker> _workers =
new List<BackgroundWorker>();
public AsynchronousExecutor(SynchronizationContext
synchronizationContext)
{
_synchronizationContext = synchronizationContext;
}
public void Execute(Action action)
{
// Exception handling is omitted, but this design
// does allow for centralized exception handling
ThreadPool.QueueUserWorkItem(o => action());
}
public void Execute(Func<Action> function)
{
ThreadPool.QueueUserWorkItem(o =>
{
Action continuation = function();
_synchronizationContext.Send(x => continuation(), null);
});
}
}

Execute(Func<Action>) メソッドはバックグラウンド スレッドで Func<Action> を呼び出し、Continuation (Func<Action> から返された Action) を受け取り、SynchronizationContext オブジェクトを使用して、Continuation をメインのユーザー インターフェイス スレッドで実行しています。

個人的には、ブロックを ICommandExecutor インターフェイスに渡す方法が気に入っています。この方法であれば、バックグラウンド プロセスを起動するためにごくわずかな形式コードしか使わないためです。以前、ラムダ式や C# の匿名デリゲートがなかった時代は、次のように、ちょっとした Command パターン クラスを使用する同様の実装がこの方法に使用されていました。

public interface ICommand
    {
        ICommand Execute();
    }

    public interface JeremysOldCommandExecutor
    {
        void Execute(ICommand command);
    }

この以前の方法の欠点は、バックグラウンド操作と表示更新のコードをモデル化するためだけに、新たに Command クラスを作成しなければならないことです。追加のクラス宣言とコンストラクター関数は、関数型の方法を使用すれば省略できるちょっとした形式コードです。しかし、私にとってもっと重要なことは、関数型の方法では、この密接に関連するコードをすべて、Presenter の 1 か所にまとめることができることです。このような小さな複数の Command クラスに分散させる必要はありません。

Continuation Passing Style (継続受け渡し形式)

Continuation (継続) の概念を基に、プログラミングの Continuation Passing Style (継続受け渡し形式) を使用して、メソッドの戻り値を待たずに、Continuation に渡すことでメソッドを呼び出すことができます。Continuation を受け取るメソッドは、Continuation を呼び出すかどうか、および呼び出すタイミングを決定します。

私の現在の Web MVC プロジェクトには、ドメイン エンティティ オブジェクトへの AJAX 呼び出しを介して、クライアント ブラウザーから送られたユーザー入力による更新を保存する多数のコントローラー アクションがあります。これらのコントローラー アクションのほとんどは、単に Repository クラスを呼び出して、変更されたエンティティを保存しますが、その他のアクションでは、他のサービスを使用して永続化作業を実行します (Repository クラスの詳細については、私の MSDN Magazine 4 月号の記事、msdn.microsoft.com/magazine/dd569757.aspx をご覧ください)。

これらのコントローラー アクションの基本のワークフローは、次のとおりです。

  1. ドメイン エンティティを検証し、検証エラーを記録します。
  2. 検証エラーがある場合は、操作が失敗したことを示す応答をクライアントに返し、クライアントで表示するために検証エラーを応答に含めます。
  3. 検証エラーがない場合は、ドメイン エンティティを保存し、操作が成功したことを示す応答をクライアントに返します。

ここでは、基本のワークフローは集約しながら、個々のコントローラー アクションは維持して、実際の永続化方法に多様性を持たせたいと考えています。現在、私のチームでは、各サブクラスがオーバーライドして基本の動作の追加や変更が可能な多数のテンプレート メソッドを使用して、CrudController<T> スーパークラスから継承を行うことで、これを実現しています。この方針は最初はうまくいきましたが、バリエーションが増えるにつれて、急速に機能しなくなりつつあります。現在、私のチームでは、コントローラー アクションを次のようなインターフェイスにデリゲートすることで、Continuation Passing Style コードを使用する方法に移行しつつあります。

 

public interface IPersistor
    {
        CrudReport Persist<T>(T target, Action<T> saveAction);
        CrudReport Persist<T>(T target);
    }

通常のコントローラー アクションでは、IPersistor に基本の CRUD ワークフローを実行するよう指示し、IPersistor が実際に目的のオブジェクトを保存するために使用できる Continuation を提供します。図 7 は、IPersistor を呼び出しますが、実際の永続化には基本の Repository ではなく別のサービスを使用するサンプルのコントローラー アクションを示しています。

図 7 サンプルのコントローラー アクション

public class SolutionController
{
private readonly IPersistor _persistor;
private readonly IWorkflowService _service;
public SolutionController(IPersistor persistor, IWorkflowService
service)
{
_persistor = persistor;
_service = service;
}
// UpdateSolutionViewModel is a data bag with the user
// input from the browser
public CrudReport Create(UpdateSolutionViewModel update)
{
var solution = new Solution();
// Push the data from the incoming
// user request into the new Solution
// object
update.ToSolution(solution);
// Persist the new Solution object, if it's valid
return _persistor.Persist(solution, x => _service.Create(x));
}
}

ここで重要なことは、SolutionController によって提供された Continuation を呼び出すかどうかと、いつ呼び出すかを IPersistor 自体が決めることです。図 8 は、IPersistor の実装例を示しています。

図 8 IPersistor の実装例

public class Persistor : IPersistor
{
private readonly IValidator _validator;
private readonly IRepository _repository;
public Persistor(IValidator validator, IRepository repository)
{
_validator = validator;
_repository = repository;
}
public CrudReport Persist<T>(T target, Action<T> saveAction)
{
// Validate the "target" object and get a report of all
// validation errors
Notification notification = _validator.Validate(target);
// If the validation fails, do NOT save the object.
// Instead, return a CrudReport with the validation errors
// and the "success" flag set to false
if (!notification.IsValid())
{
return new CrudReport()
{
Notification = notification,
success = false
};
}
// Proceed to saving the target using the Continuation supplied
// by the client of IPersistor
saveAction(target);
// return a CrudReport denoting success
return new CrudReport()
{
success = true
};
}
public CrudReport Persist<T>(T target)
{
return Persist(target, x => _repository.Save(x));
}
}

記述するコード量の削減

正直なところ、このトピックを選んだそもそもの理由は、関数型プログラミングについてと、これを C# または Visual Basic 内でも応用できる方法についてもっと学びたかったためです。このコラムを書く過程で、関数型プログラミングの手法が、一般の日常的な作業において実に便利に利用できることが非常によくわかりました。私が到達した最も重要な結論、そしてここでお伝えしようとしたことは、他の手法と比べて、関数型プログラミングの方法は、記述するコードが少なくて済むことが多く、しかも一部の作業ではより宣言型のコードを作成できる場合が多いということです。これは、ほぼどんな場合でもありがたいことです。

Jeremy Miller は、C# の Microsoft MVP であり、.NET での依存関係のインジェクション用に公開されているオープン ソースの StructureMap (structuremap.sourceforge.net) ツール、および .NET での FIT テスト用に近日公開予定の意欲的な StoryTeller (storyteller.tigris.org) ツールの作成者でもあります。彼のブログ「The Shade Tree Developer (日陰の開発者)」は、CodeBetter サイト上の codebetter.com/blogs/jeremy.miller で公開されています。