Refactoring in funzioni pure

Per le trasformazioni funzionali pure, è fondamentale comprendere come eseguire il refactoring del codice utilizzando funzioni pure.

Nota

La nomenclatura comune nella programmazione funzionale prevede il refactoring dei programmi tramite funzioni pure. In Visual Basic e C++, ciò è in linea con l'utilizzo di funzioni nei rispettivi linguaggi. Tuttavia, in C# le funzioni sono denominate metodi. Ai fini del presente documento, una funzione pura viene implementata come metodo in C#.

Come accennato in precedenza, una funzione pura prevede due caratteristiche utili:

  • Non ha effetti collaterali. La funzione non cambia le variabili o i dati di qualsiasi tipo all'esterno della funzione.

  • È coerente. Dato lo stesso set di dati di input, restituirà sempre lo stesso valore di output.

Per passare alla programmazione funzionale, è possibile eseguire il refactoring del codice esistente per eliminare inutili effetti collaterali e dipendenze esterne. In questo modo, è possibile creare versioni di funzioni pure del codice esistente.

In questo argomento vengono descritte le caratteristiche presenti e non presenti in una funzione pura. Nell'esercitazione Modifica di informazioni in un documento WordprocessingML viene illustrato come modificare un documento WordprocessingML e vengono forniti due esempi su come eseguire il refactoring tramite una funzione pura.

Eliminazione di effetti collaterali e dipendenze esterne

Negli esempi seguenti vengono confrontate due funzioni non pure e una funzione pura.

Funzione non pura che modifica un membro di classe

Nel codice seguente la funzione HypenatedConcat non è una funzione pura perché modifica il membro dati aMember nella classe:

public class Program
{
    private static string aMember = "StringOne";

    public static void HypenatedConcat(string appendStr)
    {
        aMember += '-' + appendStr;
    }

    public static void Main()
    {
        HypenatedConcat("StringTwo");
        Console.WriteLine(aMember);
    }
}
Module Module1
    Dim aMember As String = "StringOne"

    Public Sub HypenatedConcat(ByVal appendStr As String)
        aMember = aMember & "-" & appendStr
    End Sub

    Sub Main()
        HypenatedConcat("StringTwo")
        Console.WriteLine(aMember)
    End Sub
End Module

L'output del codice è il seguente:

StringOne-StringTwo

Si noti che è irrilevante se i dati modificati hanno accesso public o private o se sono un membro static (shared) o un membro di istanza. Una funzione pura non modifica i dati all'eterno della funzione.

Funzione non pura che modifica un argomento

Inoltre, la versione seguente di questa stessa funzione non è pura, perché modifica il contenuto del parametro, sb.

public class Program
{
    public static void HypenatedConcat(StringBuilder sb, String appendStr)
    {
        sb.Append('-' + appendStr);
    }

    public static void Main()
    {
        StringBuilder sb1 = new StringBuilder("StringOne");
        HypenatedConcat(sb1, "StringTwo");
        Console.WriteLine(sb1);
    }
}
Module Module1
    Public Sub HypenatedConcat(ByVal sb As StringBuilder, ByVal appendStr As String)
        sb.Append("-" & appendStr)
    End Sub

    Sub Main()
        Dim sb1 As StringBuilder = New StringBuilder("StringOne")
        HypenatedConcat(sb1, "StringTwo")
        Console.WriteLine(sb1)
    End Sub
End Module

Questa versione del programma produce lo stesso output della prima versione, perché la funzione HypenatedConcat ha modificato il valore (stato) del primo parametro richiamando la funzione del membro Append. Si noti che questa modifica si verifica nonostante il fatto che HypenatedConcat utilizza il passaggio di parametri in base a chiamata per valore.

Nota importanteImportante

Per i tipi di riferimenti, se un parametro viene passato per valore, il risultato è una copia del riferimento a un oggetto passato. Questa copia è ancora associata agli stessi dati di istanza del riferimento originale, finché la variabile di riferimento non viene assegnata a un nuovo oggetto. La chiamata per riferimento non è necessariamente richiesta affinché una funzione modifichi un parametro.

Funzione pura

Nella versione successiva del programma viene illustrato come implementare la funzione HypenatedConcat come funzione pura.

class Program
{
    public static string HyphenatedConcat(string s, string appendStr)
    {
        return (s + '-' + appendStr);
    }

    public static void Main(string[] args)
    {
        string s1 = "StringOne";
        string s2 = HyphenatedConcat(s1, "StringTwo");
        Console.WriteLine(s2);
    }
}
Module Module1
    Public Function HyphenatedConcat(ByVal s As String, ByVal appendStr As String) As String
        Return (s & "-" & appendStr)
    End Function

    Sub Main()
        Dim s1 As String = "StringOne"
        Dim s2 As String = HyphenatedConcat(s1, "StringTwo")
        Console.WriteLine(s2)
    End Sub
End Module

Anche in questo caso, la versione produce la stessa riga di output: StringOne-StringTwo Si noti che per mantenere il valore concatenato, viene archiviato nella variabile intermedia s2.

Un approccio che può risultare utile consiste nello scrivere funzioni localmente non pure, ossia che dichiarano e modificano variabili locali, ma globalmente pure. Tali funzioni presentano molte caratteristiche utili in termini di componibilità, ma evitano alcune delle complessità dei linguaggi di programmazione funzionali, ad esempio la necessità di utilizzare la ricorsione quando con un semplice ciclo si otterrebbe lo stesso risultato.

Operatori di query standard

Una caratteristica importante degli operatori di query standard è che vengono implementati come funzioni pure.

Per ulteriori informazioni, vedere Cenni preliminari sugli operatori di query standard.

Vedere anche

Concetti

Introduzione alle trasformazioni funzionali pure

Differenze tra programmazione funzionale e programmazione imperativa