7

多くのホーム プロジェクトで徐々に F# に切り替えていますが、完全なアプリケーションをどのように接続するか、特に分野横断的な懸念事項について少し困惑しています。

C# で何かをログに記録したい場合は、依存性注入を使用して ILogger を各クラスに渡し、これをコードから簡単に呼び出すことができます。モックを渡して検証することで、特定の状況でログが書き込まれることをテストで検証できます。

public class MyClass
{
    readonly ILogger _logger;
    public MyClass(ILogger logger)
    {
        _logger = logger;
    }

    public int Divide(int x, int y)
    {
        if(y == 0)
        {
            _logger.Warn("y was 0");
            return 0;
        }
        return x / y;
    }
}

F# では、モジュールをより多く使用しているため、上記は次のようになります。

module Stuff

let divde x y =
    match y with 
    | 0 -> 0
    | _ -> x / y

Logging というモジュールがあれば、それを開いて、y が 0 の場合にそこからログ関数を使用することができますが、単体テストのためにこれをどのように注入すればよいでしょうか?

各関数にログ関数 (文字列 -> 単位) を使用させ、部分的なアプリケーションを使用してそれらを接続することもできますが、実際の呼び出しをログ呼び出し内にラップする新しい関数を作成する場合と同様に、非常に多くの作業のように思えます。それを行うことができる特定のパターンまたは私が見逃している F# のビットはありますか? (私は kprintf 関数を見てきましたが、完全なアプリケーションの具体的な実装を使用しながら、さまざまなテスト シナリオで関数を指定する方法をまだ知りません)

同様に、データを取得したリポジトリをどのようにスタブ化しますか? いくつかのクラスをインスタンス化し、それに CRUD 関数を設定する必要がありますか、または (#define を除いて) 開いているモジュールを挿入する方法はありますか?

4

3 に答える 3

3

これは基本的な答えです。まず、クラスとモジュールは交換可能であると考えているようです。クラスはデータをカプセル化します。その意味で、クラスはレコードや DU に似ています。一方、モジュールは機能をカプセル化します (静的クラスにコンパイルされます)。したがって、オプションについてはすでに言及されていると思います。部分的な関数の適用、関数をデータとして渡す、または...依存関係の挿入。あなたの特定のケースでは、あなたが持っているものを保持するのが最も簡単なようです.

別の方法は、プリプロセッサ ディレクティブを使用して、さまざまなモジュールを含めることです。

#if TESTING 
open LogA
#else
open LogB
#endif

DI は必ずしも関数型言語に適合しないわけではありません。F# を使用すると、たとえば C# よりもインターフェイスの定義と実行がさらに簡単になります。

于 2011-09-20T02:05:55.410 に答える
3

実行時にロガーを変更する必要がない場合は、コンパイラ ディレクティブを使用するか#if、ロガーの 2 つの実装から選択すること (Daniel が提案) がおそらく最善かつ最も簡単な方法です。

機能の観点からは、依存性注入は、ログ機能によってすべてのコードをパラメーター化することと同じことを意味します。悪い点は、関数をどこにでも伝播する必要があることです (そして、コードが少し面倒になります)。また、モジュール内で可変グローバル変数を作成し、それを何らかのILoggerインターフェイスのインスタンスに設定することもできます。この変数を変更する必要があるのは数か所のみであるため、これは実際には F# にとって非常に受け入れられるソリューションだと思います。

別の (より「純粋な」) 代替手段は、ロギング用のワークフロー(別名モナド) を定義することです。これは、すべてのコードを F# で記述する場合にのみ適しています。この例は、無料サンプルとして入手できる私の本の第 12 章で説明されています。次に、次のように書くことができます。

let write(s) = log {
  do! logMessage("writing: " + s)
  Console.Write(s) }

let read() = log {
  do! logMessage("reading")
  return Console.ReadLine() }

let testIt() = log {
  do! logMessage("starting")
  do! write("Enter name: ")
  let! name = read()
  return "Hello " + name + "!" }

このアプローチの良いところは、優れた機能的手法であることです。コードのテストは簡単なはずLog<'TResult>です。本質的に返される関数は値とその副作用の記録を提供するため、結果を比較するだけで済みます。log { .. }ただし、ロギングを使用するすべての計算をブロックでラップする必要があるため、やり過ぎかもしれません。

于 2011-09-20T02:37:43.540 に答える
0

これは、最初の C# に似た F# での C# コードの実装です。

module stuff

type MyClass(logger:#Ilogger) =
    member x.Divide a b =
        if b=0 then 
            logger.Warn("y was 0")
            0
        else
            x/y

これにより、ロギングに C# で知っているのと同じ手法を使用できるようになります。

于 2011-09-20T06:08:09.520 に答える