Composición de funciones (informática) - Function composition (computer science)
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 posteriormente. 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, se conectan en una tubería , 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 :
float 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 ventaja de los formularios altamente compuestos, ya que requieren menos líneas de código, minimizando el "área de superficie" de un programa. DeMarco y Lister verifican empíricamente una relación inversa entre área de superficie y mantenibilidad. 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 :
g f
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 :
float foo(float x) {
return f(g(x));
}
(la forma larga con intermedios también funcionaría). Ejemplo en Forth :
: foo g f ;
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 <stdio.h>
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 size, int x)
{
for (int i=0; i<size; 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 . g
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) ; if no argument is given, evaluates to the identity function
(lambda (x) ((car fs) ((apply compose (cdr fs)) x)))))
; examples
(define (add-a-bang str)
(string-append str "!"))
(define givebang
(compose string->symbol add-a-bang symbol->string))
(givebang 'set) ; ===> set!
; anonymous composition
((compose 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 .
# the implementation has a slightly different line here because it cheats
proto sub infix:<∘> (&?, &?) is equiv(&[~]) is assoc<left> {*}
multi sub infix:<∘> () { *.self } # allows `[∘] @array` to work when `@array` is empty
multi sub infix:<∘> (&f) { &f } # allows `[∘] @array` to work when `@array` has one element
multi sub infix:<∘> (&f, &g --> Block) {
(&f).count > 1
?? -> |args { f |g |args }
!! -> |args { f g |args }
}
# alias it to the "Texas" spelling ( everything is bigger, and ASCII in Texas )
my &infix:<o> := &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):
# Available since Python v2.6
from functools import reduce
def compose(*funcs) -> int:
"""Compose a group of functions (f(g(h(...)))) into a single composite func."""
return reduce(lambda f, g: lambda x: f(g(x)), funcs)
# Example
f = lambda x: x + 1
g = lambda x: x * 2
h = lambda x: x - 3
# Call the function 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:
function o(f, g) {
return function(x) {
return f(g(x));
}
}
// Alternatively, using the rest operator and lambda expressions in 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:
// Call example:
// var c = Compose(f, g);
//
// Func<int, bool> g = _ => ...
// Func<bool, string> f = _ => ...
Func<TIn, TOut> Compose<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(other_fn)
->(*as) { other_fn.call(call(*as)) }
end
alias_method :+, :compose
end
f = ->(x) { x * 2 }
g = ->(x) { x ** 3 }
(f + g).call(12) # => 13824
Sin embargo, se introdujo un operador de composición de función nativa en Ruby 2.6:
f = proc{|x| x + 2}
g = proc{|x| x * 3}
(f << g).call(3) # -> 11; identical to f(g(3))
(f >> g).call(3) # -> 15; identical to g(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 son conductos bien definidos 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
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 CS1 maint: parámetro desalentado ( enlace ) .
- 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 .