Le funzioni di ordine superiore in FP possono essere interpretate come una sorta di iniezione di dipendenza?

7

Secondo questo articolo , in programmazione orientata agli oggetti / design iniezione di dipendenza implica

  • un consumatore dipendente,
  • una dichiarazione delle dipendenze di un componente, definite come contratti di interfaccia,
  • un injector che crea istanze di classi che implementano una determinata interfaccia di dipendenza su richiesta.

Consideriamo ora una funzione di ordine superiore in un linguaggio di programmazione funzionale, ad es. la funzione Haskell

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

da Data.List . Questa funzione trasforma un elenco in un altro elenco e, per eseguire il suo lavoro, utilizza (consuma) una funzione di predicato esterna che deve essere fornita dal relativo chiamante, ad es. l'espressione

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

seleziona tutti i numeri pari dalla lista di input.

Ma questa costruzione non è molto simile al modello illustrato sopra, dove

  • la funzione filter è il consumatore dipendente ,
  • la firma (a -> Bool) dell'argomento della funzione è il contratto di interfaccia ,
  • l'espressione che utilizza l'ordine superiore è il injector che, in questo caso particolare, inietta l'implementazione (\x -> (mod x 2) == 0) del contratto.

Più in generale, è possibile correlare le funzioni di ordine superiore e il loro modello di utilizzo nella programmazione funzionale al modello di iniezione delle dipendenze nei linguaggi orientati agli oggetti?

O nella direzione opposta, l'iniezione di dipendenza può essere paragonata all'utilizzo di qualche tipo di funzione di ordine superiore?

    
posta Giorgio 24.10.2012 - 11:17
fonte

2 risposte

8

Un concetto importante nella programmazione è la distinzione tra variabili vincolate e libere.

L'iniezione delle dipendenze riguarda la conversione di cose libere in cose associate. Ciò rende il codice più generale, più facile da testare, più facile da deridere, riduce l'accoppiamento perché il codice sa meno delle sue dipendenze, ecc.

Esempi - prima con cose liberi, quindi con cose vincolate:

In questo esempio, il primo Foo sa che Bar ha un costruttore zero-arg e conosce il tipo esatto dell'istanza. Il secondo esempio non sa nulla su come viene creato bar , e potrebbe anche ottenere una sottoclasse:

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

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

In questo esempio, pred appare libera in myFilter ma è vincolata in filter . Pertanto myFilter è molto meno generale. (Nota a margine: il valore [] e la funzione : appaiono liberi in entrambe le funzioni - se fossero invece vincolati, il risultato potrebbe essere ancora più generale!):

-- 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

Ecco un esempio a livello di codice. Il costruttore di tipi [] appare libero in map , mentre in fmap , viene associato lo specifico Functor . (spero che l'esempio non sia troppo confuso; map stesso è una funzione di ordine superiore :))

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

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

In entrambi gli esempi, il codice con meno variabili libere e più vincolati è una versione più generale.

tl, dr; sì, l'iniezione di dipendenza è simile alle funzioni di ordine superiore.

Altri esempi che dimostrano variabili vincolate contro libere (perché penso che siano interessanti):

Questa funzione non ha variabili libere, perché ogni variabile utilizzata nel corpo è un parametro:

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

In questo esempio, f e g sono vincolati dal punto di vista di (.) , ma liberi da P.O.V. dell'estrazione lambda ( \x -> f (g x) ):

(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)
    
risposta data 24.10.2012 - 17:23
fonte
3

Sì, il tuo esempio rappresenta sicuramente una semplice forma di iniezione di dipendenza.

Non è molto impressionante in questa forma - puoi fare lo stesso in un linguaggio OO avendo metodi che prendono le loro dipendenze come parametri (o classi che li richiedono nel loro costruttore). Questo diventa piuttosto ingombrante quando passi da una parte all'altra delle dipendenze. Così i contenitori di dipendenza dipendono dalla costruzione di interi grafici di oggetti con tutte le dipendenze iniettate. Non sono sicuro di quale sarebbe l'equivalente FP.

Or in the inverse direction, can dependency injection be compared to using some kind of higher-order function?

Direi di sì, sì. Il problema che l'injection delle dipendenze cerca di risolvere è la mancanza di flessibilità quando le dipendenze sono codificate, cosa che succederebbe se si avessero funzioni separate removeOddNumbersFromList , getAdminUsersFromList ecc.

    
risposta data 24.10.2012 - 11:48
fonte

Leggi altre domande sui tag