関数をパラメーターとして取る関数は、それらの関数へのパラメーターもパラメーターとして取る必要がありますか?


20

多くの場合、データアクセスを簡単にモックでき、アクセスするデータを決定するパラメーターを受け入れる署名を提供するため、このような関数を作成していることに気付きます。

public static string GetFormattedRate(
        Func<string, RateType>> getRate,
        string rateKey)
{
    var rate = getRate(rateKey);
    var formattedRate = rate.DollarsPerMonth.ToString("C0");
    return formattedRate;
}

または

public static string GetFormattedRate(
        Func<RateType, string> formatRate,
        Func<string, RateType>> getRate,
        string rateKey)
{
    var rate = getRate(rateKey);
    var formattedRate = formatRate(rate);
    return formattedRate;
}

次に、私はそれを次のように使用します:

using FormatterModule;

public static Main()
{
    var getRate = GetRateFunc(connectionStr);
    var formattedRate = GetFormattedRate(getRate, rateType);
    // or alternatively
    var formattedRate = GetFormattedRate(getRate, FormatterModule.FormatRate, rateKey);

    System.PrintLn(formattedRate);
}

これは一般的な習慣ですか?もっともっとやるべきだと思う

public static string GetFormattedRate(
        Func<RateType> getRate())
{
    var rate = getRate();
    return rate.DollarsPerMonth.ToString("C0");
}

しかし、レートタイプごとにメソッドに渡す新しい関数を作成する必要があるため、これはあまりうまく機能しないようです。

時々やるべきだと思う

public static string GetFormattedRate(RateType rate)
{
   return rate.DollarsPerMonth.ToString("C0");
}

しかし、それはフェッチとフォーマットの再利用性を奪うようです。フェッチしてフォーマットしたいときはいつでも、フェッチ用とフォーマット用の2行を書く必要があります。

関数型プログラミングについて何が欠けていますか?これはそれを行う正しい方法ですか、それとも保守と使用が簡単なより良いパターンがありますか?


50
DI癌は...これまでの広がりを持っている
IDAN Arye

16
この構造がそもそもなぜ使用されるのか、私は苦労しています。レートをパラメータとしてフォーマットするレートを返す関数を受け入れるのではなく、レートをパラメータとしてフォーマットすることを受け入れる方が確かに便利です(そして明確ですGetFormattedRate()
アロス

6
より良い方法はclosures、パラメーター自体を関数に渡す場所を利用することです。これにより、その特定のパラメーターを参照する関数が返されます。この「構成された」関数は、それを使用する関数にパラメーターとして渡されます。
トーマスジャンク

7
@IdanArye DIがん?
ジュール

11
@Jules依存性注入がん

回答:


39

これを十分に長く行うと、最終的にこの関数を何度も何度も書くことになります。

public static Type3 CombineFunc1AndFunc2(
    Func<Type1, Type2> func1,
    Func<Type2, Type3>> func2,
    Type1 input)
{
    return func2(func1(input))
}

おめでとうございます、あなたは関数合成を発明しました。

このようなラッパー関数は、1つのタイプに特化されている場合はあまり使用されません。ただし、いくつかの型変数を導入し、入力パラメーターを省略すると、GetFormattedRateの定義は次のようになります。

public static Func<A, C> Compose(
    Func<B, C> outer, Func<A, B>> inner)
{
    return (input) => outer(inner(input))
}

var GetFormattedRate = Compose(FormatRate, GetRate);
var formattedRate = GetFormattedRate(rateKey);

現状では、あなたがしていることにはほとんど目的がありません。それは一般的ではないので、あちこちでそのコードを複製する必要があります。コードはそれ自体で1000の小さな関数から必要なものすべてをアセンブルする必要があるため、コードが複雑になります。しかし、あなたの心は正しい場所にあります。これらの種類の汎用高階関数を使用して物事をまとめることに慣れる必要があります。または、有効にする古き良きファッションのラムダを使用Func<A, B>してAにをFunc<B>

繰り返してはいけません。


16
繰り返しを避けるとコードが悪化する場合は、繰り返してください。たとえば、常にこれらの2行を記述する場合FormatRate(GetRate(rateKey))
user253751

6
@immibisアイデアは、彼がGetFormattedRateこれから直接使用できるようになるということだと思います。
カールズ

これが私がここでやろうとしていることだと思いますし、以前にこのCompose関数を試したことがありますが、2番目の関数には複数のパラメータが必要になることが多いため、私はめったに使用しません。おそらく、@
thomas

@rushingeこの種の合成は、常に単一の引数を持つ典型的なFP関数で機能します(追加の引数は実際には独自の関数Func<Func<A, B>, C>です。考えてみてください)。これは、任意の機能で機能する1つのCompose機能のみが必要であることを意味します。ただし、クロージャーを使用するだけでC#関数を十分に操作Func<rateKey, rateType>できます。渡すのではなくFunc<rateType>、本当に必要であり、funcを渡すときに次のようにビルドします() => GetRate(rateKey)。ポイントは、ターゲット関数が気にしない引数を公開しないことです。
ルアーン

1
はい@immibis、Composeあなたがの実行を遅延する必要がある場合、この関数は実際にのみ有用であるGetRateように、あなたが渡したい場合など、何らかの理由でCompose(FormatRate, GetRate)、Aのすべての要素に適用するなど、独自の選択の速度を提供する機能にリスト。
14:01にjpaugh

107

関数とそのパラメーターを渡す理由はまったくなく、それらのパラメーターを使用して呼び出すだけです。実際には、あなたのケースでは、あなたは関数を渡すべき理由がないすべてでは。呼び出し元は、関数自体を呼び出して結果を渡すこともできます。

それについて考えてください-使用する代わりに:

var formattedRate = GetFormattedRate(getRate, rateType);

なぜ単に使用しないのですか:

var formattedRate = GetFormattedRate(getRate(rateType));

不要なコードを削減するだけでなく、結合も削減します。レートのフェッチ方法を変更する場合(たとえば、getRate2つの引数が必要な場合)、変更する必要はありませんGetFormattedRate

同様に、を書くGetFormattedRate(formatRate, getRate, rateKey)代わりに書く理由はありませんformatRate(getRate(rateKey))

物事を複雑にしすぎないでください。


3
この場合、あなたは正しいです。しかし、ループやマップ関数などで内部関数が複数回呼び出された場合、引数を渡す機能が役立ちます。または、@ Jackの回答で提案されている機能構成/カレーを使用します。
-user949300

15
@ user949300かもしれませんが、それはOPの使用例ではありません(もしそうなら、おそらくformatRateフォーマットされるべきレートにマップされるべきです)。
-jonrsharpe

4
@ user949300あなたの言語がクロージャをサポートしていないか、またはラムダが出てタイプするhazzleある場合にのみ場合
Bergi

4
関数とそのパラメーターを別の関数に渡すことは、怠zyなセマンティクスのない言語で評価を遅らせるための完全に有効な方法であることに注意してください。en.wikipedia.org/wiki/Thunk
Jared Smith

4
@JaredSmith関数を渡すこと、はい、そのパラメーターを渡すこと。これは、言語がクロージャーをサポートしていない場合のみです。
user253751

15

関数に追加の引数を渡すかループで呼び出すために関数に絶対に渡す必要がある場合は、代わりにラムダを渡すことができます:

public static string GetFormattedRate(
        Func<string> getRate)
{
    var rate = getRate();
    var formattedRate = rate.DollarsPerMonth.ToString("C0");
    return formattedRate;
}

var formattedRate = GetFormattedRate(()=>getRate(rateKey));

ラムダは、関数が知らない引数をバインドし、それらが存在することさえ隠します。


-1

これはあなたが望むものではありませんか?

class RateFormatter
{
    public abstract RateType GetRate(string rateKey);

    public abstract string FormatRate(RateType rate);

    public string GetFormattedRate(string rateKey)
    {
        var rate = GetRate(rateKey);
        var formattedRate = FormatRate(rate);
        return formattedRate;
    }
}

そして、次のように呼び出します:

static class Program
{
    public static void Main()
    {
        var rateFormatter = new StandardRateFormatter(connectionStr);
        var formattedRate = rateFormatter.GetFormattedRate(rateKey);

        System.PrintLn(formattedRate);
    }
}

C#などのオブジェクト指向言語で複数の異なる方法で動作できるメソッドが必要な場合、それを行う通常の方法は、メソッドに抽象メソッドを呼び出させることです。別の方法で行う特定の理由がない場合は、そのようにする必要があります。

これは良い解決策のように見えますか、それともあなたが考えている欠点がありますか?


1
あなたの答えには奇妙なものがいくつかあります(フォーマッタだけの場合、フォーマッタもレートを取得するのはなぜですか?削除することもできます GetFormattedRateメソッドを呼び出すますIRateFormatter.FormatRate(rate))。ただし、基本的な概念は正しいものであり、複数のフォーマット方法が必要な場合、OPもコードを多態的に実装する必要があると思います。
BgrWorker
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.