Come passare dalla composizione di oggetti OOP alla composizione di funzioni FP in C #

5

Ho lavorato per qualche settimana a un nuovo progetto web e sto realizzando che tutto ciò che sto facendo è fondamentalmente calcoli e trasformazioni sui dati, e che la maggior parte delle mie classi non contiene nessuno stato e potrebbe essere perfettamente trasformata in classi statiche con metodi statici. Quindi penso che questa potrebbe essere una buona opportunità per architettare l'applicazione seguendo il paradigma della programmazione funzionale. Ho prima fatto alcune ricerche su Internet per leggere le esperienze di altre persone che hanno provato qualcosa di simile e ho trovato un bel post di blog con alcune idee interessanti.

Tuttavia, mi chiedo come dovrei affrontare la situazione in cui si hanno più livelli di chiamate di funzioni annidate. Proverò a dare un semplice esempio per mostrare cosa intendo, prima in OOP e poi in FP.

In OOP di solito cerco di creare classi semplici che costruiscono funzionalità su livelli usando la composizione. Qualcosa del genere:

public class ClassLevel1
{
    private readonly IClassLevel2 _objLevel2;

    public ClassLevel1(IClassLevel2 objLevel2)
    {
        _objLevel2 = objLevel2;
    }

    public int FuncLevel1(int param1, int param2)
    {
        // Some operations here
        var res = _objLevel2.FuncLevel2(param1) + param2;
        // Some more operations here with the 'res'
        return result;
    }  
}

public class ClassLevel2
{
    private readonly IClassLevel3 _objLevel3;

    public ClassLevel2(IClassLevel3 objLevel3)
    {
        _objLevel3 = objLevel3;
    }

    public int FuncLevel2(int param1)
    {
        // Some operations here
        var res = _objLevel3.FuncLevel3(param1);
        // Some more operations here with the 'res'
        return result;
    }  
}

Quindi posso contare sull'integrazione delle dipendenze per creare le istanze e posso trarre vantaggio da qualsiasi framework di simulazione per testare facilmente ciascuna classe.

Ora immaginiamo che mi piacerebbe passare a FP e diciamo che abbiamo una funzione FuncLevel1 che internamente chiama FuncLevel2 , che internamente utilizza una terza funzione FuncLevel3 . Uno dei miei obiettivi principali sarebbe quello di strutturare il codice in modo tale che possa facilmente prendere in giro ogni funzione nei miei test di unità. Ad esempio, il codice vorrebbe questo:

public static int FuncLevel1(
    Func<Func<int, int>, int, int> FuncLevel2,
    Func<int, int> FuncLevel3,
    int param1,
    int param2)
{
    // Some operations here
    var res = FuncLevel2(FuncLevel3, param1) + param2;
    // Some more operations here with 'res'
    return result;  
}

public static int FuncLevel2(
    Func<int, int> FuncLevel3,
    int param1)
{
    // Some operations here
    var res = FuncLevel3(param1);
    // Some more operations here with 'res'
    return result;  
}

Quindi la funzione FuncLevel1 è chiamata come:

var res = FuncLevel1(FuncLevel2, FuncLevel3, 5, 10);

Il vantaggio di questa struttura è che posso prendere in giro le funzioni FuncLevel2 e FuncLevel3 se voglio unità test FuncLevel1 e FuncLevel2 rispettivamente.

In un semplice scenario mi rendo conto che potresti scrivere il codice come:

var res3 = FuncLevel3(5);
var res2 = FuncLevel2(res3) + 10;
var res1 = FuncLevel1(res2);

Ma è facile immaginare che ci possano essere altre (più complesse) situazioni in cui non è possibile risolvere il problema in questo modo.

Nel caso in cui tu abbia chiamate di funzioni profondamente annidate, passare tutte le funzioni richieste ai livelli più bassi dall'inizio alla fine può produrre un codice ingombrante. Pertanto sono molto interessato a leggere le alternative per gestire meglio uno scenario come questo.

    
posta Carlos Rodriguez 23.06.2016 - 21:38
fonte

1 risposta

0

In un linguaggio funzionale come F #, potresti sfruttare l'applicazione parziale . Ovvero, FuncLevel1 ha solo un parametro di funzione, FuncLevel2 e contiene già FuncLevel3 al suo interno:

let funcLevel1 (funcLevel2 : int -> int) (param1 : int) (param2 : int) : int =
    let result = funcLevel2 param1 + param2;
    result

let funcLevel2 (funcLevel3 : int -> int) (param1 : int) : int =
    let result = funcLevel3 param1
    result

Utilizzo:

let result = funcLevel1 (funcLevel2 funcLevel3) 5 10

In C #, puoi modellare le funzioni allo stesso modo:

public static int FuncLevel1(
    Func<int, int> FuncLevel2,
    int param1,
    int param2)
{
    var result = FuncLevel2(param1) + param2;
    return result;  
}

La domanda è come eseguire l'applicazione parziale. C # non lo supporta direttamente, ma puoi emularlo usando lambdas:

var res = FuncLevel1(i => FuncLevel2(FuncLevel3, i), 5, 10);

Un'altra opzione sarebbe scrivere un metodo di supporto che faccia l'applicazione parziale:

public static Func<T2, T3> PApply<T1, T2, T3>(Func<T1, T2, T3> func, T1 arg1)
{
    return arg2 => func(arg1, arg2);
}

Ma quando provi ad usarlo:

var res = FuncLevel1(PApply(FuncLevel2, FuncLevel3), 5, 10);

ottieni un errore in fase di compilazione per non essere in grado di dedurre argomenti di tipo. Questo errore è risolvibile, ma si traduce in un codice peggiore rispetto alla versione lambda, quindi rimango fedele a questo.

    
risposta data 24.06.2016 - 15:27
fonte