Le funzioni che prendono le funzioni come parametri, devono prendere anche i parametri di quelle funzioni come parametri?


20

Mi ritrovo spesso a scrivere funzioni simili a queste perché mi consentono di deridere facilmente l'accesso ai dati e forniscono comunque una firma che accetta i parametri per determinare a quali dati accedere.

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

O

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

Quindi lo uso in questo modo:

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

È una pratica comune? Sento che dovrei fare qualcosa di più simile

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

Ma questo non sembra funzionare molto bene perché dovrei creare una nuova funzione per passare al metodo per ogni tipo di tariffa.

A volte mi sento come se dovessi farlo

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

Ma questo sembra togliere qualsiasi riutilizzo e formattazione. Ogni volta che voglio recuperare e formattare devo scrivere due righe, una per recuperare e una per formattare.

Cosa mi manca della programmazione funzionale? È questo il modo giusto di farlo o esiste un modello migliore che sia facile da mantenere e da usare?


50
Il cancro della DI si è diffuso finora ...
Idan Arye,

16
Faccio fatica a capire perché questa struttura dovrebbe essere utilizzata in primo luogo. Sicuramente è più conveniente (e chiaro ) GetFormattedRate()accettare la tariffa da formattare come parametro, invece di farla accettare una funzione che restituisce la velocità da formattare come parametro?
aroth

6
Un modo migliore è utilizzare il punto in closurescui si passa il parametro stesso a una funzione, che a sua volta fornisce una funzione che si riferisce a quel parametro specifico. Questa funzione "configurata" verrebbe passata come parametro alla funzione, che la utilizza.
Thomas Junk,

7
@IdanArye DI cancer?
Jules,

11
@Jules dependency injection cancer
cat

Risposte:


39

Se lo fai abbastanza a lungo, alla fine ti ritroverai a scrivere questa funzione ancora e ancora:

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

Complimenti, hai inventato la composizione delle funzioni .

Le funzioni wrapper come questa non sono molto utili quando sono specializzate in un tipo. Tuttavia, se si introducono alcune variabili di tipo e si omette il parametro di input, la definizione GetFormattedRate è simile alla seguente:

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

Così com'è, quello che stai facendo ha poco scopo. Non è generico, quindi è necessario duplicare quel codice ovunque. Complica in modo eccessivo il tuo codice perché ora il tuo codice deve assemblare tutto ciò di cui ha bisogno da mille minuscole funzioni da solo. Il tuo cuore è nel posto giusto: devi solo abituarti a usare questo tipo di funzioni generiche di ordine superiore per mettere insieme le cose. Oppure, usa un buon vecchio stile lambda per trasformarti Func<A, B>e trasformarlo AinFunc<B> .

Non ripeterti.


16
Ripeti se evitare di ripetere te stesso peggiora il codice. Ad esempio se scrivi sempre queste due righe anziché FormatRate(GetRate(rateKey)).
user253751

6
@immibis Immagino che l'idea sia che potrà usarlo GetFormattedRatedirettamente da ora in poi.
Carles,

Penso che questo sia quello che sto cercando di fare qui, e ho già provato questa funzione Compose ma sembra che raramente riesco a usarla perché la mia seconda funzione spesso richiede più di un parametro. Forse ho bisogno di farlo in combinazione con le chiusure per le funzioni configurate come menzionato da @ thomas-junk
rushinge

@rushinge Questo tipo di composizione funziona sulla tipica funzione FP che ha sempre un singolo argomento (argomenti aggiuntivi sono in realtà funzioni proprie, pensala come Func<Func<A, B>, C>); questo significa che hai solo bisogno di una funzione Compose che funzioni per qualsiasi funzione. Tuttavia, puoi lavorare con le funzioni di C # abbastanza bene usando solo le chiusure - invece di passare Func<rateKey, rateType>, ti serve davvero solo Func<rateType>e quando passi la funzione, la costruisci come () => GetRate(rateKey). Il punto è che non si espongono gli argomenti di cui la funzione target non si preoccupa.
Luaan,

1
Sì, la Composefunzione è davvero utile solo se è necessario ritardare l'esecuzione di GetRateper qualche motivo, ad esempio se si desidera passare Compose(FormatRate, GetRate)a una funzione che fornisce una velocità di propria scelta, ad esempio per applicarla a ogni elemento in un elenco.
jpaugh

107

Non c'è assolutamente alcun motivo per passare una funzione e i suoi parametri, solo per poi chiamarla con quei parametri. In effetti, nel tuo caso non hai motivo di passare una funzione a tutti . Il chiamante potrebbe anche semplicemente chiamare la funzione stessa e passare il risultato.

Pensaci - invece di usare:

var formattedRate = GetFormattedRate(getRate, rateType);

perché non usare semplicemente:

var formattedRate = GetFormattedRate(getRate(rateType));

?

Oltre a ridurre il codice non necessario, riduce anche l'accoppiamento: se si desidera modificare il modo in cui viene recuperata la frequenza (ad esempio, se getRateora sono necessari due argomenti) non è necessario modificareGetFormattedRate .

Allo stesso modo, non c'è motivo di scrivere GetFormattedRate(formatRate, getRate, rateKey)invece di scrivere formatRate(getRate(rateKey)).

Non complicare eccessivamente le cose.


3
In questo caso hai ragione. Ma se la funzione interna fosse chiamata più volte, diciamo in un ciclo o in una funzione della mappa, sarebbe utile la possibilità di passare argomenti. Oppure usa la composizione funzionale / curry come proposto nella risposta @Jack.
user949300

15
@utente949300 forse, ma non è questo il caso d'uso dell'OP (e se lo era, forse è formatRateche dovrebbe essere mappato sulle tariffe che dovrebbero essere formattate).
jonrsharpe,

4
@ user949300 Solo se la tua lingua non supporta le chiusure o quando i lamdas sono un'utilità per scrivere
Bergi

4
Si noti che passare una funzione e i suoi parametri ad un'altra funzione è un modo totalmente valido per ritardare la valutazione in una lingua senza semantica pigra en.wikipedia.org/wiki/Thunk
Jared Smith

4
@JaredSmith Passando la funzione, sì, passando i suoi parametri, solo se la tua lingua non supporta le chiusure.
user253751

15

Se hai assolutamente bisogno di passare una funzione nella funzione perché passa qualche argomento in più o la chiama in un ciclo, puoi invece passare un lambda:

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

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

La lambda rileverà gli argomenti di cui la funzione non è a conoscenza e nasconderà che esistono persino.


-1

Non è questo quello che vuoi?

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

E poi chiamalo così:

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

        System.PrintLn(formattedRate);
    }
}

Se si desidera un metodo che può comportarsi in diversi modi in un linguaggio orientato agli oggetti come C #, il modo normale per farlo è quello di fare in modo che il metodo chiami un metodo astratto. Se non hai un motivo specifico per farlo in modo diverso, dovresti farlo in questo modo.

Sembra una buona soluzione o ci sono degli aspetti negativi a cui stai pensando?


1
Ci sono un paio di cose traballanti nella tua risposta (perché anche il formatter ottiene la tariffa, se è solo un formatter? Potresti anche rimuovere il GetFormattedRatemetodo e semplicemente chiamare IRateFormatter.FormatRate(rate)). Il concetto di base è tuttavia corretto e penso che anche OP dovrebbe implementare il suo codice polimorficamente se ha bisogno di più metodi di formattazione.
BgrWorker,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.