Programmazione Funzionale

Programmazione Funzionale

# Funzione e azione

Funzione fornisce un titolare per funzioni anonime parametrizzate. I tipi iniziali sono gli input e l'ultimo tipo è sempre il valore restituito.

// square a number.
Func<double, double> square = (x) => { return x * x; };

// get the square root.
// note how the signature matches the built in method.
Func<double, double> squareroot = Math.Sqrt;

// provide your workings.
Func<double, double, string> workings = (x, y) => 
    string.Format("The square of {0} is {1}.", x, square(y))

Azione gli oggetti sono come metodi void, quindi hanno solo un tipo di input. Nessun risultato viene inserito nello stack di valutazione.

// right-angled triangle.
class Triangle
{
    public double a;
    public double b;
    public double h;
}

// Pythagorean theorem.
Action<Triangle> pythagoras = (x) => 
    x.h = squareroot(square(x.a) + square(x.b));

Triangle t = new Triangle { a = 3, b = 4 };
pythagoras(t);
Console.WriteLine(t.h); // 5.

# Evita riferimenti nulli

Gli sviluppatori C# ottengono molte eccezioni di riferimento null da gestire. Gli sviluppatori F# non lo fanno perché hanno il tipo Option. Un tipo Option<> (alcuni preferiscono Maybe<> come nome) fornisce un tipo restituito Some e None. Rende esplicito che un metodo potrebbe essere in procinto di restituire un record nullo.

Ad esempio, non puoi leggere quanto segue e sapere se dovrai gestire un valore nullo.

var user = _repository.GetUser(id);

Se conosci il possibile null puoi introdurre del codice standard per gestirlo.

var username = user != null ? user.Name : string.Empty;

E se invece avessimo un'Opzione<> restituita?

Option<User> maybeUser = _repository.GetUser(id);

Il codice ora rende esplicito che potremmo avere un record Nessuno restituito ed è richiesto il codice standard per verificare la presenza di Alcuni o Nessuno:

var username = maybeUser.HasValue ? maybeUser.Value.Name : string.Empty;

Il metodo seguente mostra come restituire un'opzione <>

public Option<User> GetUser(int id)
{
    var users = new List<User>
    {
        new User { Id = 1, Name = "Joe Bloggs" },
        new User { Id = 2, Name = "John Smith" }
    };

    var user = users.FirstOrDefault(user => user.Id == id);

    return user != null ? new Option<User>(user) : new Option<User>();
}

Ecco un'implementazione minima di Option<>.

public struct Option<T>
{
    private readonly T _value;

    public T Value
    {
        get
        {
            if (!HasValue)
                throw new InvalidOperationException();

            return _value;
        }
    }

    public bool HasValue
    {
        get { return _value != null; }
    }

    public Option(T value)
    {
        _value = value;
    }

    public static implicit operator Option<T>(T value)
    {
        return new Option<T>(value);
    }
}

Per dimostrare quanto sopra, AvoidNull.csx può essere eseguito con C# REPL.

Come detto, si tratta di un'implementazione minima. Una ricerca di pacchetti NuGet "Forse" mostrerà una serie di buone librerie.

# funzioni di ordine superiore

Una funzione di ordine superiore è quella che accetta un'altra funzione come argomento o restituisce una funzione (o entrambe).

Questo viene comunemente fatto con lambda, ad esempio quando si passa un predicato a una clausola LINQ Where:

var results = data.Where(p => p.Items == 0);

La clausola Where() potrebbe ricevere molti predicati diversi, il che le conferisce una notevole flessibilità.

Il passaggio di un metodo a un altro metodo viene visualizzato anche quando si implementa il modello di progettazione della strategia. Ad esempio, è possibile scegliere e passare vari metodi di ordinamento in un metodo Sort su un oggetto a seconda dei requisiti in fase di esecuzione.

# Immutabilità

L'immutabilità è comune nella programmazione funzionale e rara nella programmazione orientata agli oggetti.

Crea, ad esempio, un tipo di indirizzo con stato mutabile:

public class Address () 
{
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City  { get; set; }
}

Qualsiasi pezzo di codice potrebbe alterare qualsiasi proprietà nell'oggetto sopra.

Ora crea il tipo di indirizzo immutabile:

public class Address () 
{
    public readonly string Line1;
    public readonly string Line2;
    public readonly string City;

    public Address(string line1, string line2, string city) 
    {
        Line1 = line1;
        Line2 = line2;
        City  = city;
    }
}

Tieni presente che avere raccolte di sola lettura non rispetta l'immutabilità. Ad esempio,

public class Classroom
{
    public readonly List<Student> Students;
    
    public Classroom(List<Student> students)
    {
        Students = students;
    }
}

non è immutabile, in quanto l'utente dell'oggetto può alterare la raccolta (aggiungere o rimuovere elementi da essa). Per renderlo immutabile, è necessario utilizzare un'interfaccia come IEnumerable, che non espone metodi da aggiungere, o renderlo una ReadOnlyCollection.

public class Classroom
{
    public readonly ReadOnlyCollection<Student> Students;

    public Classroom(ReadOnlyCollection<Student> students)
    {
        Students = students;
    }
}

List<Students> list = new List<Student>();
// add students
Classroom c = new Classroom(list.AsReadOnly());   

Con l'oggetto immutabile abbiamo i seguenti vantaggi:

  • Sarà in uno stato noto (l'altro codice non può cambiarlo).
  • È thread-safe.
  • Il costruttore offre un unico posto per la convalida.
  • Sapere che l'oggetto non può essere alterato rende il codice più facile da capire.

# raccolte immutabili

Il System.Collections.Immutable Il pacchetto NuGet fornisce classi di raccolta non modificabili.

# Creazione e aggiunta di elementi

var stack = ImmutableStack.Create<int>();
var stack2 = stack.Push(1); // stack is still empty, stack2 contains 1
var stack3 = stack.Push(2); // stack2 still contains only one, stack3 has 2, 1

# Creazione utilizzando il builder

Alcune raccolte immutabili hanno un Builder classe interna che può essere utilizzata per costruire a buon mercato istanze immutabili di grandi dimensioni:

var builder = ImmutableList.CreateBuilder<int>(); // returns ImmutableList.Builder
builder.Add(1);
builder.Add(2);
var list = builder.ToImmutable();

# Creazione da un IEnumerable esistente

var numbers = Enumerable.Range(1, 5);
var list = ImmutableList.CreateRange<int>(numbers);

Elenco di tutti i tipi di raccolta immutabili:

  • System.Collections.Immutable.ImmutableArray<T>
  • System.Collections.Immutable.ImmutableDictionary<TKey,TValue>
  • System.Collections.Immutable.ImmutableHashSet<T>
  • System.Collections.Immutable.ImmutableList<T>
  • System.Collections.Immutable.ImmutableQueue<T>
  • System.Collections.Immutable.ImmutableSortedDictionary<TKey,TValue>
  • System.Collections.Immutable.ImmutableSortedSet<T>
  • System.Collections.Immutable.ImmutableStack<T>