Sollten Funktionen, die Funktionen als Parameter annehmen, auch Parameter zu diesen Funktionen als Parameter annehmen?

20

Oft schreibe ich Funktionen, die so aussehen, weil ich damit leicht den Datenzugriff verspotten und trotzdem eine Signatur bereitstellen kann, die Parameter akzeptiert, um zu bestimmen, auf welche Daten zugegriffen werden soll.

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

Oder

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

Dann benutze ich so etwas:

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);
}

Ist das eine gängige Praxis? Ich fühle mich wie ich etwas tun sollte

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

Das scheint aber nicht sehr gut zu funktionieren, da ich eine neue Funktion erstellen müsste, um für jeden Kurstyp in die Methode zu übergehen.

Manchmal habe ich das Gefühl, ich sollte es tun

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

Dies scheint jedoch die Wiederverwendbarkeit von Abrufen und Formaten zu beeinträchtigen. Wann immer ich abrufen und formatieren möchte, muss ich zwei Zeilen schreiben, eine zum Abrufen und eine zum Formatieren.

Was vermisse ich an funktionaler Programmierung? Ist dies der richtige Weg, oder gibt es ein besseres Muster, das sowohl einfach zu warten als auch zu verwenden ist?

Ansturm
quelle
50
Der DI-Krebs hat sich bisher ausgebreitet ...
Idan Arye
16
Ich kämpfe darum, warum diese Struktur überhaupt verwendet wird. Sicherlich ist es bequemer (und klarer ) GetFormattedRate(), die zu formatierende Rate als Parameter zu akzeptieren, als eine Funktion zu akzeptieren, die die zu formatierende Rate als Parameter zurückgibt.
Donnerstag,
6
Besser ist es, zu verwenden, closureswo Sie den Parameter selbst an eine Funktion übergeben, die Ihnen im Gegenzug eine Funktion gibt, die sich auf diesen bestimmten Parameter bezieht. Diese "konfigurierte" Funktion würde als Parameter an die Funktion übergeben, die sie verwendet.
Thomas Junk
7
@IdanArye DI Krebs?
Jules
11
@Jules Abhängigkeitsinjektionskrebs
Katze

Antworten:

39

Wenn Sie dies lange genug tun, werden Sie irgendwann feststellen, dass Sie diese Funktion immer wieder schreiben:

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

Herzlichen Glückwunsch, Sie haben die Funktionszusammensetzung erfunden .

Wrapper-Funktionen wie diese haben wenig Sinn, wenn sie auf einen Typ spezialisiert sind. Wenn Sie jedoch einige Typvariablen einführen und den Eingabeparameter weglassen, sieht Ihre GetFormattedRate-Definition folgendermaßen aus:

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);

So wie es aussieht, hat das, was Sie tun, wenig Sinn. Es ist nicht generisch, daher müssen Sie diesen Code überall duplizieren. Es macht Ihren Code überkompliziert, weil Ihr Code jetzt alles, was er benötigt, aus tausend kleinen Funktionen zusammensetzen muss. Ihr Herz ist jedoch an der richtigen Stelle: Sie müssen sich nur daran gewöhnen, diese Art von generischen Funktionen höherer Ordnung zu verwenden, um Dinge zusammenzusetzen. Oder verwenden Sie ein gutes altes Mode - Lambda zu drehen Func<A, B>und Ain Func<B>.

Wiederhole dich nicht.

Jack
quelle
16
Wiederholen Sie sich, wenn die Vermeidung von Wiederholungen den Code verschlimmert. Zum Beispiel, wenn Sie immer diese beiden Zeilen statt schreiben FormatRate(GetRate(rateKey)).
user253751
6
@immibis Ich denke, die Idee ist, dass er GetFormattedRatevon nun an direkt verwenden kann.
Carles
Ich denke, das ist, was ich hier versuche, und ich habe diese Compose-Funktion bereits versucht, aber es scheint, dass ich sie selten benutze, weil meine 2. Funktion oft mehr als einen Parameter benötigt. Vielleicht muss ich dies in Kombination mit Schließungen für konfigurierte Funktionen tun, wie von @ thomas-junk
rushinge am
@rushinge Diese Art der Komposition arbeitet mit der typischen FP-Funktion, die immer ein einziges Argument hat (zusätzliche Argumente sind wirklich eigene Funktionen, denken Sie daran wie Func<Func<A, B>, C>); Dies bedeutet, dass Sie nur eine Compose-Funktion benötigen, die für jede Funktion funktioniert. Sie können jedoch mit C # -Funktionen gut genug arbeiten , wenn Func<rateKey, rateType>Sie nur Closures verwenden . Der Punkt ist, dass Sie die Argumente, die die Zielfunktion nicht interessiert, nicht offenlegen. Func<rateType>() => GetRate(rateKey)
Luaan
1
@immibis Ja, die ComposeFunktion ist wirklich nur dann nützlich, wenn Sie die Ausführung aus GetRateirgendeinem Grund verzögern müssen , z. B. wenn Sie Compose(FormatRate, GetRate)an eine Funktion übergeben möchten, die eine von Ihnen festgelegte Rate bereitstellt, z. B. um sie auf jedes Element in a anzuwenden Liste.
Jpaugh
107

Es gibt absolut keinen Grund, eine Funktion und ihre Parameter zu übergeben, um sie dann mit diesen Parametern aufzurufen. Tatsächlich haben Sie in Ihrem Fall überhaupt keinen Grund, eine Funktion zu übergeben . Der Aufrufer kann auch einfach die Funktion selbst aufrufen und das Ergebnis übergeben.

Denken Sie darüber nach - anstatt Folgendes zu verwenden:

var formattedRate = GetFormattedRate(getRate, rateType);

warum nicht einfach benutzen:

var formattedRate = GetFormattedRate(getRate(rateType));

?

Es reduziert nicht nur unnötigen Code, sondern auch die Kopplung. Wenn Sie ändern möchten, wie die Rate abgerufen wird (z. B. wenn getRatejetzt zwei Argumente erforderlich sind), müssen Sie keine Änderungen vornehmen GetFormattedRate.

Ebenso gibt es keinen Grund zu schreiben, GetFormattedRate(formatRate, getRate, rateKey)anstatt zu schreiben formatRate(getRate(rateKey)).

Überkomplizieren Sie die Dinge nicht.

user253751
quelle
3
In diesem Fall sind Sie richtig. Wenn die innere Funktion jedoch mehrmals aufgerufen würde, beispielsweise in einer Schleife oder einer Map-Funktion, wäre die Möglichkeit, Argumente einzugeben, nützlich. Oder verwenden Sie die in @Jack answer vorgeschlagene funktionale Zusammensetzung / Currying.
user949300
15
@ user949300 vielleicht, aber das ist nicht der Anwendungsfall des OP (und wenn ja formatRate, sollte dies möglicherweise auf die Raten abgebildet werden, die formatiert werden sollen).
Jonrsharpe
4
@ user949300 Nur wenn Ihre Sprache Schließungen nicht unterstützt oder wenn Lamdas ein Trubel sind
Bergi
4
Beachten Sie, dass eine Funktion und deren Parameter an eine andere Funktion vorbei ist eine völlig gültige Weise zu verzögern Auswertung in einer Sprache ohne faul Semantik en.wikipedia.org/wiki/Thunk
Jared Smith
4
@JaredSmith Übergeben der Funktion, ja, Übergeben der Parameter, nur wenn Ihre Sprache keine Closures unterstützt.
user253751
15

Wenn Sie eine Funktion unbedingt an die Funktion übergeben müssen, weil sie ein zusätzliches Argument übergibt oder sie in einer Schleife aufruft, können Sie stattdessen ein Lambda übergeben:

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

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

Das Lambda bindet die Argumente, von denen die Funktion nichts weiß, und verbirgt, dass sie überhaupt existieren.

Ratschenfreak
quelle
-1

Willst du das nicht?

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;
    }
}

Und dann nenne es so:

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

        System.PrintLn(formattedRate);
    }
}

Wenn Sie eine Methode möchten, die sich in einer objektorientierten Sprache wie C # auf verschiedene Weise verhalten kann, müssen Sie die Methode normalerweise eine abstrakte Methode aufrufen. Wenn Sie keinen bestimmten Grund haben, es anders zu machen, sollten Sie es so machen.

Sieht das nach einer guten Lösung aus, oder gibt es Nachteile, an die Sie denken?

Tanner Swett
quelle
1
Ihre Antwort enthält ein paar verdammte Dinge (warum erhält der Formatierer auch die Rate, wenn es sich nur um einen Formatierer handelt? Sie können die GetFormattedRateMethode auch entfernen und einfach aufrufen IRateFormatter.FormatRate(rate)). Das Grundkonzept ist jedoch korrekt, und ich denke, OP sollte seine Code-Polymorphie implementieren, wenn er mehrere Formatierungsmethoden benötigt.
BgrWorker