En informática , la composición de funciones es un acto o mecanismo para combinar funciones simples para construir otras más complicadas. Al igual que la composición habitual de funciones en matemáticas , el resultado de cada función se pasa como argumento de la siguiente, y el resultado de la última es el resultado del todo.
Los programadores aplican con frecuencia funciones a los resultados de otras funciones, y casi todos los lenguajes de programación lo permiten. En algunos casos, la composición de funciones es interesante como una función por derecho propio, para ser utilizada más tarde. Una función de este tipo siempre se puede definir, pero los lenguajes con funciones de primera clase lo hacen más fácil.
La capacidad de componer funciones fácilmente fomenta la factorización (separación) de funciones para la mantenibilidad y la reutilización del código . De manera más general, los grandes sistemas pueden construirse componiendo programas completos.
Hablando en términos estrictos, la composición de funciones se aplica a funciones que operan en una cantidad finita de datos, cada paso lo procesa secuencialmente antes de pasarlo al siguiente. Las funciones que operan en datos potencialmente infinitos (una secuencia u otros datos codatos ) se conocen como filtros y, en cambio, están conectadas en una canalización , que es análoga a la composición de funciones y se puede ejecutar simultáneamente .
Componer llamadas a funciones
Por ejemplo, suponga que tenemos dos funciones f y g , como en z = f ( y ) e y = g ( x ) . Componerlos significa que primero calculamos y = g ( x ) , y luego usamos y para calcular z = f ( y ) . Aquí está el ejemplo en el lenguaje C :
flotar x , y , z ; // ... y = g ( x ); z = f ( y );
Los pasos se pueden combinar si no le damos un nombre al resultado intermedio:
z = f ( g ( x ));
A pesar de las diferencias de longitud, estas dos implementaciones calculan el mismo resultado. La segunda implementación requiere solo una línea de código y se la conoce coloquialmente como una forma "altamente compuesta". La legibilidad y, por tanto, la facilidad de mantenimiento es una de las ventajas de los formularios altamente compuestos, ya que requieren menos líneas de código, lo que minimiza la "superficie" de un programa. [1] DeMarco y Lister verifican empíricamente una relación inversa entre área de superficie y mantenibilidad. [2] Por otro lado, puede ser posible abusar de formas muy compuestas. Un anidamiento de demasiadas funciones puede tener el efecto contrario, haciendo que el código sea menos fácil de mantener.
En un lenguaje basado en pilas , la composición funcional es incluso más natural: se realiza mediante concatenación y suele ser el método principal de diseño de programas. El ejemplo anterior en Forth :
novia
Que tomará lo que estaba en la pila antes, aplica g, luego f, y deja el resultado en la pila. Consulte la notación de composición de sufijo para obtener la notación matemática correspondiente.
Nombrar la composición de funciones
Ahora suponga que la combinación de llamar a f () sobre el resultado de g () es frecuentemente útil, y que queremos nombrar foo () para usarla como una función por derecho propio.
En la mayoría de los lenguajes, podemos definir una nueva función implementada por composición. Ejemplo en C :
flotar foo ( flotar x ) { volver f ( g ( x )); }
(la forma larga con intermedios también funcionaría). Ejemplo en Forth :
: foo gf;
En lenguajes como C , la única forma de crear una nueva función es definirla en la fuente del programa, lo que significa que las funciones no se pueden componer en tiempo de ejecución . Sin embargo, es posible una evaluación de una composición arbitraria de funciones predefinidas :
#include typedef int FXN ( int );int f ( int x ) { return x + 1 ; } int g ( int x ) { return x * 2 ; } int h ( int x ) { return x -3 ; }int eval ( FXN * fs [], int tamaño , int x ) { para ( int i = 0 ; i < tamaño ; i ++ ) x = ( * fs [ i ]) ( x ); return x ; }int main () { // ((6 + 1) * 2) -3 = 11 FXN * arr [] = { f , g , h }; printf ( "% d \ n " , eval ( arr , 3 , 6 )); // ((6-3) * 2) +1 = 7 arr [ 2 ] = f ; arr [ 0 ] = h ; printf ( "% d \ n " , eval ( arr , 3 , 6 )); }
Composición de primera
En los lenguajes de programación funcional, la composición de funciones se puede expresar naturalmente como una función u operador de orden superior . En otros lenguajes de programación, puede escribir sus propios mecanismos para realizar la composición de funciones.
Haskell
En Haskell , el ejemplo anterior se convierte en:
foo = f. gramo
utilizando el operador de composición incorporado (.), que se puede leer como f después de g o g compuesto con f .
El operador de composición en sí mismo se puede definir en Haskell usando una expresión lambda :
( . ) :: ( b -> c ) -> ( a -> b ) -> a -> c f . g = \ x -> f ( g x )
Las primeras líneas describen el tipo de (.): Toma un par de funciones y devuelve una función. Tenga en cuenta que Haskell no requiere la especificación de los tipos exactos de entrada y salida de f y g, solo las relaciones entre ellos (f debe aceptar lo que devuelve g). Esto convierte a (.) En un operador polimórfico .
Ceceo
Variantes de Lisp , especialmente Scheme , la intercambiabilidad de código y datos junto con el tratamiento de funciones se prestan extremadamente bien para una definición recursiva de un operador composicional variable .
( define ( compose . fs ) ( if ( null? fs ) ( lambda ( x ) x ) ; si no se proporciona ningún argumento, se evalúa a la función de identidad ( lambda ( x ) (( car fs ) (( apply compose ( cdr fs )) x ))))); ejemplos ( define ( add-a-bang str ) ( string-append str "!" ))( definir givebang ( componer cadena-> símbolo agregar-a-bang símbolo-> cadena ))( conjunto de givebang ' ) ; ===> establecer!; composición anónima (( componer sqrt negate square ) 5 ) ; ===> 0 + 5i
APL
Muchos dialectos de APL incorporan una función de composición que utiliza el símbolo ∘
. Esta función de orden superior extiende la composición de funciones a la aplicación diádica de la función del lado izquierdo de tal manera que A f∘g B
sea A f g B
.
foo ← f ∘ g
Además, puede definir la composición de la función:
o ← { ⍺⍺ ⍵⍵ ⍵ }
En el dialecto que no admite la definición en línea con llaves, la definición tradicional está disponible:
∇ r ← ( f o g ) x r ← f g x ∇
Raku
Raku como Haskell tiene un operador de composición de funciones incorporado, la principal diferencia es que se escribe como ∘
o o
.
my & foo = & f ∘ & g ;
También, como Haskell , podría definir el operador usted mismo. De hecho, el siguiente es el código Raku utilizado para definirlo en la implementación de Rakudo .
# la implementación tiene una línea ligeramente diferente aquí porque engaña al proto sub infijo : <∘> (& ?, &?) is equiv (& [~]) is assoc { * }múltiples sub infija : <∘> () { *. self } # permite que `[∘] @ array` funcione cuando` @ array` está vacío multi sub infijo : <∘> (& f) { & f } # permite que `[∘] @ array` funcione cuando` @ array` tiene un elemento multi sub infijo : <∘> (& f, & g -> Bloque) { ( & f ) . contar > 1 ?? -> | args { f | g | args } !! -> | args { f g | args } }# alias a la ortografía "Texas" (todo es más grande, y ASCII en Texas) my & infix: : = & infix: <∘> ;
Pitón
En Python , una forma de definir la composición para cualquier grupo de funciones es usar la función reduce (use functools.reduce en Python 3):
# Disponible desde Python v2.6 desde functools import reducedef compose ( * funcs ) -> int : "" "Componga un grupo de funciones (f (g (h (...)))) en una sola función compuesta" "" return reduce ( lambda f , g : lambda x : f ( g ( x )), funciones )# Ejemplo f = lambda x : x + 1 g = lambda x : x * 2 h = lambda x : x - 3# Llame a la función x = 10: ((x-3) * 2) +1 = 15 print ( compose ( f , g , h ) ( 10 ))
JavaScript
En JavaScript podemos definirlo como una función que toma dos funciones f y g, y produce una función:
función o ( f , g ) { función de retorno ( x ) { retorno f ( g ( x )); } } // Alternativamente, usando el operador rest y las expresiones lambda en ES2015 const compose = (... fs ) => ( x ) => fs . reduceRight (( acc , f ) => f ( acc ), x )
C#
En C # podemos definirlo como un Func que toma dos Funcs f y g, y produce un Func:
// Ejemplo de llamada: // var c = Compose (f, g); // // Func g = _ => ... ,>// Func f = _ => ... ,>Func < TIn , TOut > Componer < TIn , TMid , TOut > ( Func < TMid , TOut > f , Func < TIn , TMid > g ) => _ => f ( g ( _ ));
Rubí
Los lenguajes como Ruby le permiten construir un operador binario usted mismo:
class Proc def compose ( otro_fn ) -> ( * como ) { otro_fn . llamada ( llamada ( * como )) } final alias_method : + , : componer finalf = -> ( x ) { x * 2 } g = -> ( x ) { x ** 3 } ( f + g ) . llamar ( 12 ) # => 13824
Sin embargo, se introdujo un operador de composición de función nativa en Ruby 2.6: [3]
f = proc { | x | x + 2 } g = proc { | x | x * 3 } ( f << g ) . llamar ( 3 ) # -> 11; idéntico af (g (3)) ( f >> g ) . llamar ( 3 ) # -> 15; idéntico ag (f (3))
Encuesta de investigación
Las nociones de composición, incluido el principio de composicionalidad y capacidad de composición , son tan omnipresentes que numerosas líneas de investigación han evolucionado por separado. La siguiente es una muestra del tipo de investigación en la que la noción de composición es central.
- Steele (1994) aplicó directamente la composición de funciones al ensamblaje de bloques de construcción conocidos como " mónadas " en el lenguaje de programación Haskell .
- Meyer (1988) abordó el problema de la reutilización del software en términos de componibilidad.
- Abadi y Lamport (1993) definieron formalmente una regla de prueba para la composición funcional que asegura la seguridad y vitalidad de un programa.
- Kracht (2001) identificó una forma reforzada de composicionalidad colocándola en un sistema semiótico y aplicándola al problema de la ambigüedad estructural que se encuentra con frecuencia en la lingüística computacional .
- van Gelder y Port (1993) examinaron el papel de la composicionalidad en los aspectos analógicos del procesamiento del lenguaje natural.
- Según una revisión de Gibbons (2002) , el tratamiento formal de la composición subyace en la validación del ensamblaje de componentes en lenguajes de programación visual como Visual Age de IBM para el lenguaje Java .
Composición a gran escala
Los programas o sistemas completos pueden tratarse como funciones, que se pueden componer fácilmente si sus entradas y salidas están bien definidas [4], que permiten una composición fácil de filtros y tuvieron tanto éxito que se convirtieron en un patrón de diseño de sistemas operativos.
Los procedimientos imperativos con efectos secundarios violan la transparencia referencial y, por lo tanto, no se pueden componer limpiamente. Sin embargo, si considera el "estado del mundo" antes y después de ejecutar el código como entrada y salida, obtendrá una función limpia. La composición de tales funciones corresponde a ejecutar los procedimientos uno tras otro. El formalismo de las mónadas utiliza esta idea para incorporar efectos secundarios y E / S en lenguajes funcionales.
Ver también
- Zurra
- Descomposición funcional
- Herencia de implementación
- Semántica de herencia
- Iteratee
- Canalización (Unix)
- Principio de composicionalidad
- Herencia virtual
Notas
- ^ Cox (1986) , págs. 15-17
- ^ DeMarco y Lister (1995) , págs. 133-135.
- ^ "Ruby 2.6.0 lanzado" . www.ruby-lang.org . Consultado el 4 de enero de 2019 .
- ^ Raymond (2003)
Referencias
- Abadi, Martín ; Lamport, Leslie (1993), "Composición de especificaciones" (PDF) , ACM Transactions on Programming Languages and Systems , 15 (1): 73-132, doi : 10.1145 / 151646.151649.
- Cox, Brad (1986), Programación orientada a objetos, un enfoque evolutivo , Lectura, MA: Addison-Wesley, ISBN 978-0-201-54834-1.
- Daume, Hal, III, otro tutorial más de Haskell.
- DeMarco, Tom ; Lister, Tim (1995), "Desarrollo de software: estado del arte frente al estado de la práctica", en DeMarco, Tom (ed.), Why Does Software Cost So Much, and other puzzles of the Information Age , Nueva York, Nueva York: Dorset House, ISBN 0-932633-34-X.
- van Gelder, Timothy ; Port, Robert (1993), "Más allá de lo simbólico: prolegómenos a un Kama-Sutra de composicionalidad", en Honavar, Vasant ; Uhr, Leonard (eds.), Procesamiento de símbolos y modelos conexionistas en inteligencia artificial y cognición: pasos hacia la integración , Academic Press.
- Gibbons, Jeremy (2002), Arbab, Farhad; Talcott, Carolyn (eds.), Proc. Quinta Conferencia Internacional sobre Modelos y Lenguajes de Coordinación (PDF) , Lecture Notes in Computer Science, 2315 , Springer-Verlag, págs. 339–350, doi : 10.1007 / 3-540-46000-4 \ _18.
- Korn, Henry; Liberi, Albert (1974), An Elementary Approach to Functions , Nueva York, NY: McGraw-Hill, ISBN 0-07-035339-5.
- Kracht, Marcus (2001), "Composicionalidad estricta y gramáticas de movimiento literal", Proc. 3er Congreso Internacional sobre Aspectos Lógicos de la Lingüística Computacional , Lecture Notes in Computer Science, 2014 , Springer-Verlag, págs. 126–143, doi : 10.1007 / 3-540-45738-0_8.
- Meyer, Bertrand (1988), Construcción de software orientado a objetos , Nueva York, NY: Prentice Hall, págs. 13-15, ISBN 0-13-629049-3.
- Miller, George A. (1956), "El número mágico siete, más o menos dos: algunos límites en nuestra capacidad para procesar información" , Psychological Review , 63 (2): 81–97, doi : 10.1037 / h0043158 , hdl : 11858 / 00-001M-0000-002C-4646-B , PMID 13310704 , archivado desde el original en 2010-06-19 , recuperado 2010-05-02.
- Pierce, Benjamin C .; Turner, David N. (2000), "Pict: Un lenguaje de programación basado en el cálculo pi", Prueba, lenguaje e interacción: Ensayos en honor a Robin Milner , Foundations Of Computing Series, Cambridge, MA: MIT Press, págs. . 455–494, ISBN 0-262-16188-5.
- Raymond, Eric S. (2003), "1.6.3 Regla de composición: Diseñar programas para conectarlos con otros programas" , El arte de la programación Unix , Addison-Wesley, pp. 15-16, ISBN 978-0-13-142901-7.
- Steele, Guy L., Jr. (1994), "Construir intérpretes componiendo mónadas" , Proc. 21º Simposio de ACM sobre principios de lenguajes de programación , págs. 472–492, doi : 10.1145 / 174675.178068.