Continuamos nuestra serie de artículos sobre programación funcional en F #. Hoy hablamos sobre la asociatividad y la composición de funciones, así como también comparamos la composición y la tubería. ¡Mira debajo del gato!

Asociatividad y composición de funciones.
Asociatividad de funciones
Supongamos que hay una cadena de funciones escritas en una fila. ¿En qué orden se combinarán?
Por ejemplo, ¿qué significa esta función?
let F xyz = xyz
¿Significa esto que la función y
debería aplicarse al argumento z
, y luego el resultado debería pasarse a x
? Es decir:
let F xyz = x (yz)
¿O se aplica la función x
al argumento y
, después de lo cual la función obtenida como resultado se evaluará con el argumento z
? Es decir:
let F xyz = (xy) z
- La segunda opción es correcta.
- El uso de funciones ha dejado asociatividad .
xyz
significa lo mismo que (xy) z
.- Y
wxyz
es igual a ((wx) y) z
. - Esto no debería verse genial.
- Ya hemos visto cómo funciona la aplicación parcial.
- Si hablamos de
x
como una función con dos parámetros, entonces (xy) z
es el resultado de una aplicación parcial del primer parámetro, seguido de pasar el argumento z
a la función intermedia.
Si necesita asociatividad correcta, puede usar paréntesis o canalización. Las siguientes tres entradas son equivalentes:
let F xyz = x (yz) let F xyz = yz |> x // let F xyz = x <| yz //
Como ejercicio, intente mostrar las firmas de estas funciones sin un cálculo real.
Composición de la función
Mencionamos la composición de funciones varias veces, pero ¿qué significa realmente este término? Parece aterrador a primera vista, pero de hecho, todo es bastante simple.
Digamos que tenemos una función "f" que asigna el tipo "T1" al tipo "T2". También tenemos una función "g" que convierte el tipo "T2" en "T3". Luego podemos conectar la salida de "f" y la entrada de "g", creando una nueva función que convierte el tipo "T1" en el tipo "T3".

Por ejemplo:
let f (x:int) = float x * 3.0 // f - int->float let g (x:float) = x > 4.0 // g - float->bool
Podemos crear una nueva función "h" que toma la salida de "f" y la usa como entrada para "g".
let h (x:int) = let y = f(x) g(y) // g
Un poco más compacto:
let h (x:int) = g ( f(x) ) // h int->bool // h 1 h 2
Hasta ahora, muy simple. Esto es interesante, podemos definir una nueva función "componer", que toma las funciones "f" y "g" y las combina sin siquiera saber sus firmas.
let compose fgx = g ( f(x) )
Después de la ejecución, puede ver que el compilador decidió correctamente que " f
" es una función del tipo genérico 'a
para el tipo genérico 'b
, y ' g
' se limita a la entrada del tipo 'b
:
val compose : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
(Tenga en cuenta que una composición generalizada de operaciones es posible solo porque cada función tiene exactamente un parámetro de entrada y una salida. Este enfoque no es posible en lenguajes no funcionales).
Como podemos ver, esta definición se utiliza para el operador " >>
".
let (>>) fgx = g ( f(x) )
Gracias a esta definición, se pueden construir nuevas funciones sobre la base de las funciones existentes utilizando la composición.
let add1 x = x + 1 let times2 x = x * 2 let add1Times2 x = (>>) add1 times2 x // add1Times2 3
La grabación explícita es muy engorrosa. Pero puede hacer que su uso sea más fácil de entender.
En primer lugar, puede deshacerse del parámetro x
, y la composición devolverá una aplicación parcial.
let add1Times2 = (>>) add1 times2
En segundo lugar, porque >>
es un operador binario, puedes ponerlo en el centro.
let add1Times2 = add1 >> times2
El uso de la composición hace que el código sea más limpio y claro.
let add1 x = x + 1 let times2 x = x * 2 // let add1Times2 x = times2(add1 x) // let add1Times2 = add1 >> times2
Usando el operador de composición en la práctica
El operador de composición (como todos los operadores de infijo) tiene una prioridad más baja que las funciones regulares. Esto significa que las funciones utilizadas en la composición pueden tener argumentos sin utilizar paréntesis.
Por ejemplo, si las funciones "agregar" y "veces" tienen parámetros, se pueden pasar durante la composición.
let add nx = x + n let times nx = x * n let add1Times2 = add 1 >> times 2 let add5Times3 = add 5 >> times 3 // add5Times3 1
Mientras las entradas y salidas correspondientes de las funciones coincidan, las funciones pueden usar cualquier valor. Por ejemplo, considere el siguiente código que ejecuta una función dos veces:
let twice f = f >> f // ('a -> 'a) -> ('a -> 'a)
Tenga en cuenta que el compilador ha inferido que " f
" acepta y devuelve valores del mismo tipo.
Ahora considere la función " +
". Como vimos anteriormente, la entrada es int
, pero la salida es en realidad (int->int)
. Por lo tanto, " +
" se puede usar en " twice
". Por lo tanto, puedes escribir:
let add1 = (+) 1 // (int -> int) let add1Twice = twice add1 // (int -> int) // add1Twice 9
Por otro lado, no puedes escribir:
let addThenMultiply = (+) >> (*)
Debido a que la entrada "*" debe ser una función int
, no una función int->int
(que es la salida de la suma).
Pero si corrige la primera función para que solo devuelva int
, todo funcionará:
let add1ThenMultiply = (+) 1 >> (*) // (+) 1 (int -> int) 'int' // add1ThenMultiply 2 7
La composición también se puede realizar en orden inverso mediante " <<
", si es necesario:
let times2Add1 = add 1 << times 2 times2Add1 3
La composición inversa se usa principalmente para hacer que el código se parezca más al inglés ("similar al inglés"). Por ejemplo:
let myList = [] myList |> List.isEmpty |> not // myList |> (not << List.isEmpty) //
Composición vs. transportador
Puede confundirse con la pequeña diferencia entre la composición y el transportador, ya que Pueden parecer muy similares.
Primero, mire la definición de una tubería:
let (|>) xf = fx
Todo esto le permite poner los argumentos de las funciones antes y no después. Eso es todo Si la función tiene varios parámetros, la entrada debe ser el último parámetro (en el conjunto actual de parámetros, y no en absoluto). Un ejemplo visto anteriormente:
let doSomething xyz = x+y+z doSomething 1 2 3 // 3 |> doSomething 1 2 //
La composición no es la misma y no puede ser un reemplazo para una tubería. En el siguiente ejemplo, incluso el número 3 no es una función, por lo que la "salida" no se puede pasar a hacer algo:
3 >> doSomething 1 2 // // f >> g g(f(x)) : doSomething 1 2 ( 3(x) ) // 3 ! // error FS0001: This expression was expected to have type 'a->'b // but here has type int
El compilador se queja de que el valor "3" debe ser un tipo de función 'a->'b
.
Compare esto con la definición de composición, que toma 3 argumentos, donde los dos primeros deben ser funciones.
let (>>) fgx = g ( f(x) ) let add nx = x + n let times nx = x * n let add1Times2 = add 1 >> times 2
Los intentos de utilizar la canalización en lugar de la composición generarán un error de compilación. En el siguiente ejemplo, " add 1
" es la función (parcial) int->int
, que no puede usarse como el segundo parámetro para " times 2
".
let add1Times2 = add 1 |> times 2 // // x |> f f(x) : let add1Times2 = times 2 (add 1) // add1 'int' // error FS0001: Type mismatch. 'int -> int' does not match 'int'
El compilador se queja de que " times 2
" debe aceptar el parámetro int->int
, es decir ser una función (int->int)->'a
.
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.