2008-08-12 5 views

Respuesta

19

(Editar: una pequeña Ocaml FP Koan a empezar las cosas)

El Koan de currying (A koan acerca de la comida, que no se trata de alimentos)

Un estudiante vino a Jacques Garrigue y dijo: "No entiendo para qué sirve el currying". Jacques respondió: "Dime tu comida favorita y tu postre favorito". El desconcertado estudiante respondió que le gustaban los okonomiyaki y los kanten, pero mientras que su restaurante favorito servía excelente okonomiyaki, sus kanten siempre le producían dolor de estómago a la mañana siguiente. Así que Jacques llevó al estudiante a comer en un restaurante que servía okonomiyaki tan bueno como el favorito del alumno, y luego lo llevó al otro lado de la ciudad a una tienda que hacía excelentes kanten donde el estudiante aplicaba alegremente el resto de su apetito. El estudiante estaba saciado, pero no estaba iluminado ... hasta la mañana siguiente cuando se despertó y su estómago se sentía bien.

Mis ejemplos cubrirán usarlo para la reutilización y la encapsulación de código. Esto es bastante obvio una vez que los veas y debería darte un ejemplo concreto y simple que puedas aplicar en numerosas situaciones.

Queremos hacer un mapa sobre un árbol. Esta función podría curry y aplicarse a cada nodo si necesita más de un argumento, ya que estaríamos aplicando uno en el nodo ya que es el argumento final. No tiene que curry, pero escribir otra función (suponiendo que esta función se esté utilizando en otras instancias con otras variables) sería un desperdicio.

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 

pero esto es lo mismo que:

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

Así que este simple caso no es convincente. Sin embargo, realmente es así, y poderoso una vez que usas el lenguaje más y te encuentras naturalmente con estas situaciones. El otro ejemplo con algunos códigos de reutilización como currying. A recurrence relation to create prime numbers.gran cantidad de similitud allí:

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, ahora Rowland y cloitre son funciones al curry, ya que tienen variables libres, y podemos conseguir cualquier índice de su secuencia sin saber o preocuparse por f_recurrence.

+3

Esta respuesta describe la aplicación de función parcial, [que está relacionado con currying, pero no es lo mismo] (http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – phoog

2

Es un proceso bastante simple. Tome una función, enlace uno de sus argumentos y devuelva una nueva función. Por ejemplo:

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

Ahora por ganarse la función concatStrings simples, usted puede agregar fácilmente un símbolo del sistema DOS estilo al frente de cualquier cadena! ¡Realmente util!

Bueno, en realidad no. Un caso más útil que encuentro es cuando quiero tener una función make que me devuelva datos de la misma manera.

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 práctica de esto es que en lugar de crear una clase entera para este tipo de cosas, llamando al constructor, llamando obj.readDWORD(), sólo tienen una función que no se puede mutar hacia fuera de Debajo de ti.

+2

Esta respuesta describe la aplicación de función parcial, [que está relacionada con el currying, pero no es lo mismo] (http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – phoog

14

Mientras que los ejemplos anteriores respondieron a la pregunta, aquí hay dos ejemplos más simples de cómo Currying puede ser beneficioso para la programación 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..." 

¡Y no olvide que puede curry la familia de funciones de Printf! En la versión curried, fíjate en la distinta falta de 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];; 
+3

Esta respuesta describe la aplicación de función parcial, [que está relacionada con currying, pero no es lo mismo] (http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application) – phoog

2

¿Sabe que puede asignar una función a una lista? Por ejemplo, la cartografía de una función para añadir una a cada elemento de una lista:

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

Esto es en realidad ya están usando currificación porque se utilizó el operador (+) para crear una función para agregar uno a su argumento, pero se puede exprimir una poco mejor este ejemplo, alterando a asignar la misma función de una lista de listas:

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

sin currying no podría aplicarse parcialmente estas funciones y tendría que escribir algo como esto en su lugar:

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

Esta respuesta describe la aplicación de función parcial, [que está relacionada con el currying, pero no es lo mismo] (http://en.wikipedia.org/wiki/ Currying # Contrast_with_partial_function_application) – phoog

+0

@phoog Esta respuesta expl correctamente afirma que "sin currículum no podría aplicar parcialmente estas funciones". –

1

He dado un buen ejemplo de la simulación de currying en C# on my blog. Lo esencial es que puede crear una función que se cierre sobre un parámetro (en mi ejemplo, crear una función para calcular el impuesto sobre las ventas cerrado sobre el valor de un municipio dado) a partir de una función multiparamétrica existente.

Lo que es atractivo aquí es que en lugar de tener que hacer una función separada específicamente para calcular el impuesto a las ventas en el condado de Cook, puede crear (y reutilizar) la función dinámicamente en tiempo de ejecución.

+0

+1, aunque el enlace de su blog parece estar roto, sospecho que su ejemplo mostró la función real de currying en C#, no simulada. Esta respuesta es la única que realmente describe el currying como lo que permite la aplicación de funciones parciales, en lugar de confundirlo con la aplicación de funciones parciales. – phoog

+0

Recientemente migré mi blog de CommunityServer a Sitefinity. No llegué a escribir una utilidad para importar los datos de mi viejo blog :(pero utilicé la sobrecarga de funciones para simular el currying. Si pasas los dos parámetros, obtienes el resultado, si pasas uno, obtienes un func que toma el segundo y devuelve el resultado. No es tan elegante como el verdadero currying, pero funciona;) –

9

Currying describe el proceso de transformar una función con múltiples argumentos en una cadena de funciones de argumento único. Ejemplo en C#, para una función de tres 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)); 
} 

Ahora, el argumento booleano es probablemente no el argumento de que había más probable quiere dejar abierta con una aplicación parcial. Esta es una razón por la cual el orden de los argumentos en las funciones F # puede parecer un poco extraño al principio. Definamos una función de C# de curry 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); 
} 

Ahora, podemos hacer algo un poco más ú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 qué son estos ejemplos en C#? Porque en F #, las declaraciones de función están currificadas de forma predeterminada. Usualmente no necesita curry funciones; ya están curry. La principal excepción a esto son los métodos de framework y otras funciones sobrecargadas, que toman una tupla que contiene sus múltiples argumentos. Por lo tanto, es posible que desee curry tales funciones, y, de hecho, me encontré con esta pregunta cuando estaba buscando una función de biblioteca que haría esto.Supongo que le falta (si es que lo es) porque es bastante trivial para aplicar:

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 moverse por el fracaso con String.Compare, ya que por lo que yo puedo decir que no hay manera de especificar qué 3- argumento sobrecargue a recoger, se puede utilizar una solución no general:

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

no voy a entrar en detalles sobre los usos de la aplicación de la función parcial en F # porque las otras respuestas han cubierto ya.

+1

Esta debería ser la respuesta aceptada –