En una publicación anterior sobre curry, vimos cómo las funciones con varios parámetros se dividen en funciones más pequeñas, con un parámetro. Esta es una solución matemáticamente correcta, pero hay otras razones para hacerlo: también conduce a una técnica muy poderosa llamada aplicación parcial de funciones . Este estilo es muy utilizado en la programación funcional, y es muy importante entenderlo.

Uso parcial de funciones.
La idea de una aplicación parcial es que si arreglamos los primeros N parámetros de la función, obtenemos una nueva función con los parámetros restantes. De la discusión sobre curry, uno podría ver cómo la aplicación parcial ocurre naturalmente.
Algunos ejemplos simples para ilustrar:
// "" + 42 let add42 = (+) 42 // add42 1 add42 3 // // [1;2;3] |> List.map add42 // "" let twoIsLessThan = (<) 2 // twoIsLessThan 1 twoIsLessThan 3 // twoIsLessThan [1;2;3] |> List.filter twoIsLessThan // "" printfn let printer = printfn "printing param=%i" // printer [1;2;3] |> List.iter printer
En cada caso, creamos una función parcialmente aplicada que puede reutilizarse en diferentes situaciones.
Y, por supuesto, la aplicación parcial hace que sea igualmente fácil corregir los parámetros de la función. Aquí hay algunos ejemplos:
// List.map let add1 = (+) 1 let add1ToEach = List.map add1 // "add1" List.map // add1ToEach [1;2;3;4] // List.filter let filterEvens = List.filter (fun i -> i%2 = 0) // // filterEvens [1;2;3;4]
El siguiente ejemplo, más complejo, ilustra cómo se puede usar el mismo enfoque para crear de manera transparente un comportamiento "incrustado".
- Creamos una función que suma dos números, pero además se necesita una función de registro que registre estos números y el resultado.
- La función de registro toma dos parámetros: (cadena) "nombre" y (genérico) "valor", por lo tanto, tiene la
string->'a->unit
firma string->'a->unit
. - Luego creamos varias implementaciones de la función de registro, como un registrador de consola o un registrador basado en ventanas emergentes.
- Y finalmente, aplicamos parcialmente la función principal para crear una nueva función, con un registrador cerrado.
// - let adderWithPluggableLogger logger xy = logger "x" x logger "y" y let result = x + y logger "x+y" result result // - let consoleLogger argName argValue = printfn "%s=%A" argName argValue // let addWithConsoleLogger = adderWithPluggableLogger consoleLogger addWithConsoleLogger 1 2 addWithConsoleLogger 42 99 // - let popupLogger argName argValue = let message = sprintf "%s=%A" argName argValue System.Windows.Forms.MessageBox.Show( text=message,caption="Logger") |> ignore // - let addWithPopupLogger = adderWithPluggableLogger popupLogger addWithPopupLogger 1 2 addWithPopupLogger 42 99
Estas funciones de registrador cerrado se pueden usar como cualquier otra función. Por ejemplo, podemos crear una aplicación parcial para agregar 42, y luego pasarla a la función de lista, como lo hicimos para la función add42
simple.
// 42 let add42WithConsoleLogger = addWithConsoleLogger 42 [1;2;3] |> List.map add42WithConsoleLogger [1;2;3] |> List.map add42 //
Las funciones aplicadas parcialmente son una herramienta muy útil. Podemos crear funciones de biblioteca flexibles (aunque complejas), y es fácil hacerlas reutilizables de forma predeterminada, de modo que la complejidad se oculte del código del cliente.
Diseño de funciones parciales
Obviamente, el orden de los parámetros puede afectar seriamente la conveniencia del uso parcial. Por ejemplo, la mayoría de las funciones de List
, como List.map
y List.filter
tienen una forma similar, a saber:
List-function [function parameter(s)] [list]
La lista es siempre el último parámetro. Algunos ejemplos en forma completa:
List.map (fun i -> i+1) [0;1;2;3] List.filter (fun i -> i>1) [0;1;2;3] List.sortBy (fun i -> -i ) [0;1;2;3]
Los mismos ejemplos con aplicación parcial:
let eachAdd1 = List.map (fun i -> i+1) eachAdd1 [0;1;2;3] let excludeOneOrLess = List.filter (fun i -> i>1) excludeOneOrLess [0;1;2;3] let sortDesc = List.sortBy (fun i -> -i) sortDesc [0;1;2;3]
Si las funciones de la biblioteca se implementaran con un orden diferente de argumentos, la aplicación parcial sería mucho menos conveniente.
Cuando escribe su función con muchos parámetros, puede pensar en su mejor orden. Como con todos los problemas de diseño, no hay una respuesta "correcta", pero hay varias recomendaciones generalmente aceptadas.
- Comience con opciones que probablemente sean estáticas.
- Sé el último en establecer estructuras de datos o colecciones (u otros parámetros cambiantes)
- Para una mejor comprensión de operaciones como la resta, es aconsejable observar el orden esperado
El primer consejo es simple. Los parámetros que probablemente se "arreglen" mediante una aplicación parcial deben ir primero, como en los ejemplos con el registrador anterior.
Seguir la segunda sugerencia facilita el uso del operador y la composición de la tubería. Ya lo hemos visto muchas veces en los ejemplos con funciones de las listas anteriores.
// let result = [1..10] |> List.map (fun i -> i+1) |> List.filter (fun i -> i>5)
Del mismo modo, las funciones aplicadas parciales sobre listas se exponen fácilmente a la composición, porque El parámetro de lista se puede omitir:
let compositeOp = List.map (fun i -> i+1) >> List.filter (fun i -> i>5) let result = compositeOp [1..10]
Ajuste parcial de la función BCL
Las funciones de la biblioteca de clases base .NET (BCL) de .NET son fácilmente accesibles desde F #, pero están diseñadas sin depender de lenguajes funcionales como F #. Por ejemplo, la mayoría de las funciones requieren un parámetro de datos al principio, mientras que en F # el parámetro de datos generalmente debería ser el último.
Sin embargo, puede escribir fácilmente envoltorios para hacer que estas funciones sean más idiomáticas. En el siguiente ejemplo, las funciones de cadena .NET se reescriben para que la cadena de destino se use en último lugar en lugar de primero:
// .NET let replace oldStr newStr (s:string) = s.Replace(oldValue=oldStr, newValue=newStr) let startsWith lookFor (s:string) = s.StartsWith(lookFor)
Después de que la cadena se haya convertido en el último parámetro, puede usar estas funciones en las tuberías, como de costumbre:
let result = "hello" |> replace "h" "j" |> startsWith "j" ["the"; "quick"; "brown"; "fox"] |> List.filter (startsWith "f")
o en la composición de funciones:
let compositeOp = replace "h" "j" >> startsWith "j" let result = compositeOp "hello"
Comprensión del operador del transportador
Después de haber visto una aplicación parcial en los negocios, puede comprender cómo funcionan las funciones canalizadas.
La función de canalización se define de la siguiente manera:
let (|>) xf = fx
Todo lo que hace es poner un argumento antes de la función, no después.
let doSomething xyz = x+y+z doSomething 1 2 3 //
En el caso en que la función f
tiene varios parámetros, y el último parámetro de la función f
actuará como el valor de entrada x
tubería. De hecho, la función transferida f
ya se ha aplicado parcialmente y espera solo un parámetro: el valor de entrada para la canalización (te x
).
Aquí hay un ejemplo similar reescrito para uso parcial.
let doSomething xy = let intermediateFn z = x+y+z intermediateFn // intermediateFn let doSomethingPartial = doSomething 1 2 doSomethingPartial 3 // 3 |> doSomethingPartial // ,
Como ya ha visto, el operador canalizado es extremadamente común en F # y se usa cuando desea preservar el flujo natural de datos. Algunos ejemplos más que puede haber encontrado:
"12" |> int // "12" int 1 |> (+) 2 |> (*) 3 //
Operador de transportador inverso
De vez en cuando, puede encontrar el operador de canalización inversa "<|".
let (<|) fx = fx
Esta función parece no hacer nada, entonces, ¿por qué existe?
La razón es que cuando el operador de canalización inversa se usa como un operador binario de estilo infijo, reduce la necesidad de paréntesis, lo que hace que el código sea más limpio.
printf "%i" 1+2 // printf "%i" (1+2) // printf "%i" <| 1+2 //
Puede usar tuberías en dos direcciones a la vez para obtener la notación pseudoinfijada.
let add xy = x + y (1+2) add (3+4) // 1+2 |> add <| 3+4 //
Recursos Adicionales
Hay muchos tutoriales para F #, incluidos los materiales para aquellos que vienen con experiencia en C # o Java. Los siguientes enlaces pueden ser útiles a medida que profundiza en F #:
También se describen varias otras formas de comenzar a aprender F # .
Finalmente, la comunidad F # es muy amigable para principiantes. Hay un chat muy activo en Slack, respaldado por la F # Software Foundation, con salas para principiantes a las que puedes unirte libremente . ¡Recomendamos encarecidamente que haga esto!
¡No te olvides de visitar el sitio de la comunidad de habla rusa F # ! Si tiene alguna pregunta sobre el aprendizaje de un idioma, estaremos encantados de discutirlo en las salas de chat:
Sobre autores de traducción
Traducido por @kleidemos
La traducción y los cambios editoriales fueron realizados por los esfuerzos de la comunidad de desarrolladores de F # de habla rusa . También agradecemos a @schvepsss y @shwars por preparar este artículo para su publicación.