¿Se pueden interpretar las funciones de orden superior en FP como algún tipo de inyección de dependencia?

7

Según este artículo , en la programación orientada a objetos / diseño inyección de dependencia implica

  • un consumidor dependiente,
  • una declaración de las dependencias de un componente, definidas como contratos de interfaz,
  • un inyector que crea instancias de clases que implementan una interfaz de dependencia dada a solicitud.

Consideremos ahora una función de orden superior en un lenguaje de programación funcional, por ejemplo, la función de Haskell

filter :: (a -> Bool) -> [a] -> [a]

desde Data.List . Esta función transforma una lista en otra lista y, para realizar su trabajo, utiliza (consume) una función de predicado externa que debe ser proporcionada por su llamante, por ejemplo. la expresion

filter (\x -> (mod x 2) == 0) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

selecciona todos los números pares de la lista de entrada.

Pero no es esta construcción muy similar al patrón ilustrado arriba, donde

  • la función filter es el consumidor dependiente ,
  • la firma (a -> Bool) del argumento de la función es el contrato de interfaz ,
  • la expresión que utiliza el orden superior es el inyector que, en este caso particular, inyecta la implementación (\x -> (mod x 2) == 0) del contrato.

Más en general, ¿se pueden relacionar las funciones de orden superior y su patrón de uso en la programación funcional con el patrón de inyección de dependencia en lenguajes orientados a objetos?

O en la dirección inversa, ¿se puede comparar la inyección de dependencia con el uso de algún tipo de función de orden superior?

    
pregunta Giorgio 24.10.2012 - 11:17

2 respuestas

8

Un concepto importante en la programación es la distinción entre variables unidas y libres.

La inyección de dependencia se ocupa de convertir cosas libres en cosas enlazadas. Esto hace que el código sea más general, más fácil de probar, más fácil de simular, reduce el acoplamiento porque el código sabe menos acerca de sus dependencias, etc.

Ejemplos: primero con cosas libres , luego con cosas :

En este ejemplo, el primer Foo sabe que Bar tiene un constructor de argumento cero, y conoce el tipo exacto de la instancia. El segundo ejemplo no sabe nada sobre cómo se crea bar , e incluso podría estar obteniendo una subclase:

class Foo {
    private Bar bar;
    Foo() {
        this.bar = new Bar();
    }
} 

class Foo{
    private Bar bar;
    Foo(Bar bar) {
        this.bar = bar;
    }
} 

En este ejemplo, pred parece libre en myFilter pero vinculado en filter . Así, myFilter es mucho menos general. (Nota al margen: el valor [] y la función : aparecen libres en ambas funciones; si estuvieran vinculados, ¡el resultado podría ser aún más general!):

-- assuming 'pred :: Integer -> Bool' is defined elsewhere
myFilter :: [Integer] -> [Integer]
myFilter  [] = []
myFilter (x:xs)
  | pred x        = x : myFilter xs
  | otherwise     = myFilter xs

filter :: (a -> Bool) -> [a] -> [a]
filter   _    []    = []
filter pred (x:xs)
  | pred x         = x : filter pred xs
  | otherwise      = filter pred xs

Aquí hay un ejemplo de nivel de tipo. El constructor de tipo [] aparece libre en map , mientras que en fmap , el Functor específico está vinculado. (espero que el ejemplo no sea demasiado confuso; map en sí mismo es una función de orden superior. :))

map :: (a -> b) -> [a] -> [b]

fmap :: Functor f => (a -> b) -> f a -> f b

En ambos ejemplos, el código con menos variables libres y más encuadernadas es una versión más general.

tl, dr; sí, la inyección de dependencia es similar a las funciones de orden superior.

Más ejemplos que demuestran variables ligadas frente a libres (porque creo que son interesantes):

Esta función no tiene variables libres, porque cada variable que se usa en el cuerpo es un parámetro:

flip                    :: (a -> b -> c) -> b -> a -> c
flip f x y              =  f y x

En este ejemplo, f y g están vinculados desde el punto de vista de (.) , pero libres de la P.O.V. de la abstracción lambda ( \x -> f (g x) ):

(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)
    
respondido por el user39685 24.10.2012 - 17:23
3

Sí, su ejemplo definitivamente representa una forma simple de inyección de dependencia.

No es muy impresionante en esa forma, puedes hacer lo mismo en un lenguaje OO al tener métodos que toman sus dependencias como parámetros (o clases que los requieren en su constructor). Esto se vuelve un poco difícil de manejar cuando se pasan las dependencias en todas partes. Por lo tanto, los contenedores de inyección de dependencia son responsables de construir gráficos de objetos completos con todas las dependencias inyectadas. No estoy seguro de cuál sería el equivalente de FP.

  

O en la dirección inversa, ¿se puede comparar la inyección de dependencia con el uso de algún tipo de función de orden superior?

Yo diría que sí, sí. El problema que la inyección de dependencias intenta resolver es la falta de flexibilidad cuando las dependencias están codificadas, lo que sucedería si tuviera funciones separadas removeOddNumbersFromList , getAdminUsersFromList etc.

    
respondido por el Michael Borgwardt 24.10.2012 - 11:48

Lea otras preguntas en las etiquetas