Pergunta

Alguém tem um exemplo decente, de preferência prático/útil, que possa postar demonstrando o conceito?

Foi útil?

Solução

(Editar:um pequeno Ocaml FP Koan para começar as coisas)

O Koan do Currying (Um koan sobre comida, que não é sobre comida)

Um aluno veio a Jacques Garrigue e disse: "Não entendo para que serve o curry". Jacques respondeu: "Conte -me sua refeição favorita e sua sobremesa favorita".O estudante intrigado respondeu que gostava de okonomiyaki e kanten, mas embora seu restaurante favorito servisse ótimos okonomiyaki, o kanten deles sempre lhe causava dor de estômago na manhã seguinte.Então Jacques levou o aluno para comer em um restaurante que servia okonomiyaki tão bom quanto o favorito do aluno, depois o levou para o outro lado da cidade, até uma loja que fazia kanten excelente, onde o aluno aplicou alegremente o que restava de seu apetite.O aluno estava saciado, mas não estava iluminado...até a manhã seguinte, quando ele acordou e seu estômago estava bem.

Meus exemplos abordarão seu uso para reutilização e encapsulamento de código.Isso é bastante óbvio quando você olha para eles e deve fornecer um exemplo concreto e simples que você pode aplicar em diversas situações.

Queremos fazer um mapa sobre uma árvore.Esta função pode ser curry e aplicada a cada nó se precisar de mais de um argumento - já que estaríamos aplicando aquele no nó como argumento final.Não precisa ser curry, mas escrever outro função (assumindo que esta função está sendo usada em outras instâncias com outras variáveis) seria um desperdício.

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

mas isso é o mesmo que:

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

Portanto, este caso simples não é convincente.Porém, é realmente poderoso quando você usa mais a linguagem e se depara naturalmente com essas situações.O outro exemplo com alguma reutilização de código como currying.A relação de recorrência para criar números primos.Muita semelhança aí:

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, agora rowland e cloitre são funções curried, pois possuem variáveis ​​​​livres, e podemos obter qualquer índice de sua sequência sem saber ou nos preocupar com f_recurrence.

Outras dicas

Embora os exemplos anteriores tenham respondido à pergunta, aqui estão dois exemplos mais simples de como o Currying pode ser benéfico para a programação em 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 não se esqueça que você pode curry a família de funções Printf!Na versão ao curry, observe a nítida falta de 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];;

Currying descreve o processo de transformação de uma função com múltiplos argumentos em uma cadeia de funções de argumento único.Exemplo em C#, para uma função de três argumentos:

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

Agora, o argumento booleano é provavelmente não o argumento que você provavelmente gostaria de deixar em aberto com uma aplicação parcial.Esse é um dos motivos pelos quais a ordem dos argumentos nas funções F# pode parecer um pouco estranha à primeira vista.Vamos definir uma função curry C# diferente:

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

Agora podemos fazer algo um pouco mais útil:

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

Por que esses exemplos estão em C#?Porque em F#, as declarações de função são curry por padrão.Normalmente você não precisa curry funções;eles já estão com curry.A principal exceção a isso são os métodos de estrutura e outras funções sobrecarregadas, que recebem uma tupla contendo seus múltiplos argumentos.Portanto, você pode querer usar essas funções e, de fato, me deparei com essa questão quando estava procurando uma função de biblioteca que fizesse isso.Suponho que esteja faltando (se é que está) porque é bastante trivial de implementar:

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"

Para contornar a falha com String.Compare, já que, pelo que sei, não há como especificar qual sobrecarga de 3 argumentos escolher, você pode usar uma solução não geral:

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

Não entrarei em detalhes sobre os usos do aplicativo de função parcial em F# porque as outras respostas já abordaram isso.

É um processo bastante simples.Pegue uma função, vincule um de seus argumentos e retorne uma nova função.Por exemplo:

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

Agora, ao usar a função concatStrings simples, você pode facilmente adicionar um prompt de comando estilo DOS na frente de qualquer string!Muito útil!

Ok, na verdade não.Um caso mais útil que encontro é quando desejo criar uma função que me retorne dados de maneira semelhante a um fluxo.

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.

A parte conveniente disso é que, em vez de criar uma classe inteira para esse tipo de coisa, chamando o construtor, chamando obj.readDWORD(), você apenas tem uma função que não pode sofrer mutação sob você.

Você sabe que pode mapear uma função em uma lista?Por exemplo, mapeando uma função para adicionar uma a cada elemento de uma lista:

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

Na verdade, isso já está usando curry porque o (+) foi usado para criar uma função para adicionar um ao seu argumento, mas você pode extrair um pouco mais deste exemplo alterando-o para mapear a mesma função de uma lista de listas:

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

Sem curry você não poderia aplicar parcialmente essas funções e teria que escrever algo assim:

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

Dei um bom exemplo de simulação de curry em C# no meu blog.A essência é que você pode criar uma função que é fechada sobre um parâmetro (no meu exemplo, criar uma função para calcular o imposto sobre vendas fechado sobre o valor de um determinado município) a partir de uma função multiparâmetro existente.

O que é atraente aqui é que, em vez de criar uma função separada especificamente para calcular o imposto sobre vendas no Condado de Cook, você pode criar (e reutilizar) a função dinamicamente em tempo de execução.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top