La funzione "funzione ordine superiore" consente / mantiene l'astrazione e l'incapsulamento?

6

Sotto è la funzione repeat scritta usando un paradigma funzionale, in modo tale che quando chiamata come repeat(square, 2)(5) applicherà la square funzione 2 volte sul numero 5 , qualcosa come square(square(5)) .

def repeat(f, n):
    def identity(x):
        return x
    def apply_n_times(n):
        def recursive_apply(x):
            return apply_n_times(n - 1)(f(x))

        if n < 0:
            raise ValueError("Cannot apply a function %d times" % (n))
        elif n == 0:
            return identity 
        else:
            return recursive_apply
    return apply_n_times(n)

def square(x):
    return mul(x, x)

Riguardo all'astrazione, vedo che repeat(square, 2) restituisce un dettaglio di implementazione sotto forma di apply_n_times(n - 1)(f(x)) più volte prima di fornire il risultato effettivo.

Per quanto riguarda l'incapsulamento, per l'espressione f = repeat(square, 2) si potrebbero mutare i membri dell'oggetto funzione, ad esempio: f.__name__='garbage'

Il concetto di higher order function consente di supportare l'astrazione e l'incapsulamento? Perché restituiscono i dettagli dell'implementazione e forniscono l'accesso per la mutazione.

Tali implementazioni esistenti nei software di grandi dimensioni sono molto noiose da usare, in quanto l'utente deve avere un'idea dell'implementazione prima di utilizzarla.

    
posta overexchange 16.02.2015 - 13:23
fonte

3 risposte

14

Wrt abstraction, I see that repeat(square, 2) returns implementation detail in the form of apply_n_times(n - 1)(f(x)) multiple times before providing the actual result.

La funzione restituita da repeat(square, 2) non è un dettaglio di implementazione; è l'intero punto di chiamare repeat . Un dettaglio di implementazione è qualcosa di cui il chiamante non ha bisogno di sapere (e nella maggior parte dei casi non è autorizzato a sapere nulla di entrambi) e che potrebbe essere cambiato senza rompere il codice del chiamante. Il chiamante desidera e ha bisogno della funzione restituita da repeat .

Mi sembra che tu stia guardando repeat come se il suo scopo fosse quello di darti il risultato dell'applicazione di f , ed è per questo che vedi il fatto che restituisce apply_n_times come dettaglio di implementazione. Se si desidera solo il risultato di comporre f con se stesso n volte, è possibile definire repeat per prendere tre argomenti. Ma ciò che rende utili le funzioni curry è il fatto che puoi utilizzare le funzioni intermedie che crea! Per es.

numbers = range(1, 10)
numbersSquaredTwice = map(repeat(square, 2), numbers)

In altre parole, il loro scopo non è calcolare il risultato finale, ma calcolare una funzione che può calcolare il risultato finale. La funzione restituita da repeat è una cosa utile da avere di per sé.

With regards to encapsulation, for expression f = repeat(square, 2) one could mutate the members of function object, for example: f.name='garbage'

Tutto in Python ha metadati che possono essere controllati e modificati, e gli oggetti funzione non fanno eccezione. Python non ti impedirà di ispezionare e manipolare profondamente ogni dato nel programma. Ciò ti consente di rompere quasi ogni astrazione se scegli di farlo. Quindi potresti dire che è Python che non ha un ottimo supporto per l'incapsulamento. Nei linguaggi funzionali una funzione non "conosce il suo nome", perché chiedere una funzione per il suo nome ha tanto senso quanto chiedere un intero per il suo nome. Le funzioni non hanno bisogno di nomi e se scegli di dartene una, non hanno bisogno di conoscerla.

Ma tieni presente che ogni chiamata a repeat produce un oggetto funzione nuovo , quindi anche se scegli di lasciar perdere, le modifiche apportate non influiranno sui valori di ritorno di altri chiama a repeat .

    
risposta data 16.02.2015 - 14:03
fonte
7

Il vantaggio dell'astrazione è che il chiamante non ha avere informazioni sui dettagli di implementazione. Se capisco correttamente, stai mettendo in discussione questo costrutto python perché il chiamante può trovare informazioni sui dettagli di implementazione. Non è la stessa cosa

Non ho bisogno di sapere su come un metodo fa il suo lavoro è utile. Aiuta lo sviluppatore del software nel loro lavoro, perché puoi pensare a un livello di dettaglio alla volta invece di dover manipolare due di essi contemporaneamente.

Non riuscire a scoprirlo è qualcosa di completamente diverso. Può essere utile dare al programmatore della biblioteca una maggiore libertà per un'ottimizzazione successiva senza rompere nulla. Ma in pratica, è sufficiente dichiarare semplicemente nella propria API "Qualsiasi tipo intermedio visibile tramite l'oggetto helper restituito non fa parte del contratto e il loro utilizzo è un comportamento non definito". In questo modo lo scrittore della biblioteca conserva la stessa libertà e solo i chiamanti che violano volontariamente il contratto esplicito ne soffrono, mentre il vantaggio principale (essere in grado di ignorare i dettagli) rimane invariato.

    
risposta data 16.02.2015 - 13:32
fonte
5

Il chiamante non ha così tanto bisogno di conoscere i dettagli di implementazione di repeat(f, n) in quanto ha bisogno di sapere il suo valore di ritorno . In questo caso, il valore restituito è un po 'più complicato della maggior parte, perché restituisce una funzione. In un linguaggio statico come Haskell, questo è facilmente documentato nella firma della funzione come:

repeat :: (a -> a) -> Int -> (a -> a)

Questo ti dice che restituisce una funzione che accetta un a e restituisce un a , ma non è necessario conoscere i dettagli di implementazione di quella funzione, solo la sua firma e semantica.

La principale debolezza dei linguaggi dinamici come Python è che i loro tipi, inclusi i loro tipi di ritorno, non sono documentati nelle loro firme di funzioni (il punto di forza è che il programmatore non è costretto a documentarle). Ciò ti consente di documentare i tipi di ritorno in un commento / docstring o di leggere il corpo della funzione o gli indizi contestuali nel codice chiamante per determinarli. Hai sempre dovuto farlo con le funzioni tutte , non solo quelle di ordine superiore. Probabilmente non te ne sei accorto perché le tue funzioni sono state generalmente abbastanza piccole o abbastanza semplici da non essere problematiche e il codice di terze parti complesso che hai utilizzato ha generalmente delle buone docstring.

Python ha anche una grande tradizione di informazioni che vengono nascoste per convenzione, non secondo il dettato della lingua. Se non riesci a capire se qualcosa dovrebbe essere un dettaglio di implementazione privato o meno, devi documentarlo anche tu. Generalmente considero il corpo di ogni chiusura come un dettaglio di implementazione privato, che sia restituito o meno.

    
risposta data 16.02.2015 - 14:32
fonte