Domanda

Qualcuno ha un esempio decente, preferibilmente pratico/utile, da pubblicare per dimostrare il concetto?

È stato utile?

Soluzione

(Modificare:un piccolo Ocaml FP Koan per iniziare le cose)

Il Koan del Curry (Un koan sul cibo, che non riguarda il cibo)

Uno studente è venuto a Jacques Garrigue e ha detto: "Non capisco a cosa serve il curry". Jacques rispose: "Dimmi il tuo pasto preferito e il tuo dessert preferito".Lo studente, perplesso, rispose che gli piacevano l'okonomiyaki e il kanten, ma mentre il suo ristorante preferito serviva un ottimo okonomiyaki, il loro kanten gli faceva sempre venire il mal di pancia la mattina seguente.Così Jacques portò lo studente a mangiare in un ristorante che serviva okonomiyaki altrettanto buono del suo preferito, poi lo portò dall'altra parte della città in un negozio che produceva ottimi kanten dove lo studente applicò felicemente il resto del suo appetito.Lo studente era sazio, ma non era illuminato...fino al mattino dopo, quando si svegliò e il suo stomaco si sentì bene.

I miei esempi riguarderanno l'utilizzo per il riutilizzo e l'incapsulamento del codice.Ciò è abbastanza ovvio una volta che li guardi e dovrebbe darti un esempio concreto e semplice che puoi pensare di applicare in numerose situazioni.

Vogliamo fare una mappa sopra un albero.Questa funzione potrebbe essere sottoposta a currying e applicata a ciascun nodo se necessita di più di un argomento, poiché applicheremmo quello sul nodo come argomento finale.Non deve essere al curry, ma scrivere un altro funzione (supponendo che questa funzione venga utilizzata in altri casi con altre variabili) sarebbe uno spreco.

type 'a tree = E of 'a | N of 'a * 'a tree * 'a tree
let rec tree_map f tree = match tree with
    | N(x,left,right) -> N(f x, tree_map f left, tree_map f right)
    | E(x) -> E(f x)

let sample_tree = N(1,E(3),E(4)
let multiply x y = x * y
let sample_tree2 = tree_map (multiply 3) sample_tree

ma questo è lo stesso di:

let sample_tree2 = tree_map (fun x -> x * 3) sample_tree

Quindi questo semplice caso non è convincente.Lo è davvero ed è potente una volta che usi di più la lingua e ti imbatti in queste situazioni in modo naturale.L'altro esempio con qualche riutilizzo del codice come currying.UN relazione di ricorrenza per creare numeri primi.Ci sono un sacco di somiglianze lì dentro:

let rec f_recurrence f a seed n =
    match n with
    | a -> seed
    | _ -> let prev = f_recurrence f a seed (n-1) in
           prev + (f n prev)

let rowland = f_recurrence gcd 1 7
let cloitre = f_recurrence lcm 1 1

let rowland_prime n = (rowland (n+1)) - (rowland n)
let cloitre_prime n = ((cloitre (n+1))/(cloitre n)) - 1

Ok, ora rowland e cloitre sono funzioni al curry, poiché hanno variabili libere e possiamo ottenere qualsiasi indice della sua sequenza senza sapere o preoccuparci di f_recurrence.

Altri suggerimenti

Mentre gli esempi precedenti hanno risposto alla domanda, ecco due esempi più semplici di come il currying può essere utile per la programmazione F#.

open System.IO

let appendFile (fileName : string) (text : string) =
    let file = new StreamWriter(fileName, true)
    file.WriteLine(text)
    file.Close()

// Call it normally    
appendFile @"D:\Log.txt" "Processing Event X..."

// If you curry the function, you don't need to keep specifying the
// log file name.
let curriedAppendFile = appendFile @"D:\Log.txt"

// Adds data to "Log.txt"
curriedAppendFile "Processing Event Y..."

E non dimenticare che puoi sfruttare la famiglia di funzioni Printf!Nella versione al curry si nota la netta mancanza di una lambda.

// Non curried, Prints 1 2 3 
List.iter (fun i -> printf "%d " i) [1 .. 3];;

// Curried, Prints 1 2 3
List.iter (printfn "%d ") [1 .. 3];;

Il currying descrive il processo di trasformazione di una funzione con più argomenti in una catena di funzioni ad argomento singolo.Esempio in C#, per una funzione a tre argomenti:

Func<T1, Func<T2, Func<T3, T4>>> Curry<T1, T2, T3, T4>(Func<T1, T2, T3, T4> f)
{
    return a => b => c => f(a, b, c);
}

void UseACurriedFunction()
{
    var curryCompare = Curry<string, string, bool, int>(String.Compare);
    var a = "SomeString";
    var b = "SOMESTRING";
    Console.WriteLine(String.Compare(a, b, true));
    Console.WriteLine(curryCompare(a)(b)(true));

    //partial application
    var compareAWithB = curryCompare(a)(b);
    Console.WriteLine(compareAWithB(true));
    Console.WriteLine(compareAWithB(false));
}

Ora, l'argomento booleano è probabilmente non l'argomento che molto probabilmente vorrai lasciare aperto con un'applicazione parziale.Questo è uno dei motivi per cui all'inizio l'ordine degli argomenti nelle funzioni F# può sembrare un po' strano.Definiamo una diversa funzione curry C#:

Func<T3, Func<T2, Func<T1, T4>>> BackwardsCurry<T1, T2, T3, T4>(Func<T1, T2, T3, T4> f)
{
    return a => b => c => f(c, b, a);
}

Ora possiamo fare qualcosa di un po’ più utile:

void UseADifferentlyCurriedFunction()
{
    var curryCompare = BackwardsCurry<string, string, bool, int>(String.Compare);

    var caseSensitiveCompare = curryCompare(false);
    var caseInsensitiveCompare = curryCompare(true);

    var format = Curry<string, string, string, string>(String.Format)("Results of comparing {0} with {1}:");

    var strings = new[] {"Hello", "HELLO", "Greetings", "GREETINGS"};

    foreach (var s in strings)
    {
        var caseSensitiveCompareWithS = caseSensitiveCompare(s);
        var caseInsensitiveCompareWithS = caseInsensitiveCompare(s);
        var formatWithS = format(s);

        foreach (var t in strings)
        {
            Console.WriteLine(formatWithS(t));
            Console.WriteLine(caseSensitiveCompareWithS(t));
            Console.WriteLine(caseInsensitiveCompareWithS(t));
        }
    }
}

Perché questi esempi sono in C#?Perché in F# le dichiarazioni di funzione vengono eseguite per impostazione predefinita.Di solito non è necessario occuparsi delle funzioni;sono già al curry.La principale eccezione a ciò sono i metodi framework e altre funzioni sovraccaricate, che accettano una tupla contenente i loro molteplici argomenti.Potresti quindi voler eseguire tali funzioni e, in effetti, mi sono imbattuto in questa domanda mentre cercavo una funzione di libreria che potesse farlo.Suppongo che manchi (se effettivamente lo è) perché è piuttosto banale da implementare:

let curry f a b c = f(a, b, c)

//overload resolution failure: there are two overloads with three arguments.
//let curryCompare = curry String.Compare

//This one might be more useful; it works because there's only one 3-argument overload
let backCurry f a b c = f(c, b, a)
let intParse = backCurry Int32.Parse
let intParseCurrentCultureAnyStyle = intParse CultureInfo.CurrentCulture NumberStyles.Any
let myInt = intParseCurrentCultureAnyStyle "23"
let myOtherInt = intParseCurrentCultureAnyStyle "42"

Per aggirare l'errore con String.Compare, poiché per quanto ne so non c'è modo di specificare quale sovraccarico di 3 argomenti scegliere, puoi utilizzare una soluzione non generale:

let curryCompare s1 s2 (b:bool) = String.Compare(s1, s2, b)
let backwardsCurryCompare (b:bool) s1 s2 = String.Compare(s1, s2, b)

Non entrerò nei dettagli sugli usi dell'applicazione di funzioni parziali in F# perché le altre risposte lo hanno già trattato.

È un processo abbastanza semplice.Prendi una funzione, associa uno dei suoi argomenti e restituisce una nuova funzione.Per esempio:

let concatStrings left right = left + right
let makeCommandPrompt= appendString "c:\> "

Ora, eseguendo la semplice funzione concatStrings, puoi facilmente aggiungere un prompt dei comandi in stile DOS all'inizio di qualsiasi stringa!Davvero utile!

Ok, non proprio.Un caso più utile che trovo è quando voglio avere una funzione make a che mi restituisca i dati in un flusso simile.

let readDWORD array i = array[i] | array[i + 1] << 8 | array[i + 2] << 16 | 
    array[i + 3] << 24 //I've actually used this function in Python.

La parte conveniente è che invece di creare un'intera classe per questo genere di cose, chiamando il costruttore, chiamando obj.readDWORD(), hai semplicemente una funzione che non può essere modificata da sotto di te.

Sai che puoi mappare una funzione su un elenco?Ad esempio, mappando una funzione per aggiungerne uno a ciascun elemento di un elenco:

> List.map ((+) 1) [1; 2; 3];;
val it : int list = [2; 3; 4]

Questo in realtà sta già usando il curry perché il (+) L'operatore è stato utilizzato per creare una funzione per aggiungerne uno al suo argomento, ma puoi ottenere qualcosa in più da questo esempio modificandolo per mappare la stessa funzione di un elenco di elenchi:

> List.map (List.map ((+) 1)) [[1; 2]; [3]];;
val it : int list = [[2; 3]; [4]]

Senza insistere non potresti applicare parzialmente queste funzioni e dovresti invece scrivere qualcosa del genere:

> List.map((fun xs -> List.map((fun n -> n + 1), xs)), [[1; 2]; [3]]);;
val it : int list = [[2; 3]; [4]]

Ho fornito un buon esempio di simulazione del currying in C# sul mio blog.L'essenza è che è possibile creare una funzione chiusa su un parametro (nel mio esempio creare una funzione per il calcolo dell'imposta sulle vendite chiusa sul valore di un determinato comune) da una funzione multiparametrica esistente.

Ciò che è interessante in questo caso è che invece di dover creare una funzione separata specifica per il calcolo dell'imposta sulle vendite nella Contea di Cook, è possibile creare (e riutilizzare) la funzione in modo dinamico in fase di runtime.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top