15

Molte volte mi sono imbattuto in dichiarazioni del modulo X non funziona bene.Cosa significa per qualcosa "comporre bene"?

posso ricordare alcuni esempi che ho letto di recente:

  • macro non compongono bene (contesto: clojure)
  • Locks non compongono bene (contesto: clojure)
  • La programmazione imperativa non si compone bene ... ecc.

Voglio capire le implicazioni della componibilità in termini di progettazione/lettura/scrittura del codice? Gli esempi sarebbero carini.

+3

possibile duplicato di [Che cosa significa compositività nel contesto della programmazione funzionale?] (Http://stackoverflow.com/questions/2887013/what-does-composability-mean-in-context-of-functional- programmazione) – missingfaktor

risposta

10

In sostanza, le funzioni di "composizione" significano semplicemente l'unione di due o più funzioni per creare una grande funzione che combina le loro funzionalità in un modo utile. In sostanza, si definisce una sequenza di funzioni e si inseriscono i risultati di ognuno nel successivo, dando infine il risultato dell'intero processo. Clojure fornisce la funzione comp per farlo, puoi farlo anche a mano.

Le funzioni che è possibile concatenare con altre funzioni in modo creativo sono in generale più utili delle funzioni che è possibile chiamare solo in determinate condizioni. Ad esempio, se non avessimo la funzione last e avessimo solo le tradizionali funzioni di elenco Lisp, potremmo facilmente definire last come (def last (comp first reverse)). Guarda che non abbiamo nemmeno bisogno di defn o menzionare alcun argomento, perché stiamo solo collegando il risultato di una funzione in un'altra. Ciò non funzionerebbe se, ad esempio, reverse adottasse la rotta imperativa di modificare la sequenza sul posto. Anche i macro sono problematici perché non è possibile passarli a funzioni come comp o apply.

+1

Un esempio di macro che non sta componendo bene. Diciamo che volevamo fare qualcosa di simile. (riduci e [vero vero falso vero]). Purtroppo perché ed è una macro non possiamo passarla per ridurla. – Glen

2

La composizione (nel contesto che si descrive a livello funzionale) è in genere la capacità di alimentare una funzione in un'altra pulita e senza elaborazione intermedia. Un tale esempio di composizione è in std :: cout in C++:

Cout < < ciascuno < < elemento < < pubblicitari < < su;

Questo è un semplice esempio di composizione che non sembra "simile" alla composizione.

altro esempio con una forma più visibilmente compositiva:

foo (bar (baz()));

Wikipedia Link

composizione è utile per la leggibilità e compattezza, tuttavia concatenamento grandi collezioni di funzioni di interblocco che potenzialmente possono restituire codici di errore o spazzatura dati possono essere pericolosi (questo è il motivo per cui è preferibile minimizzare codice di errore o di ritorno nullo valori.)

A condizione che le proprie funzioni utilizzino eccezioni o in alternativa restituiscano null objects è possibile ridurre al minimo il requisito di ramificazione (se) sugli errori e massimizzare il potenziale di composizione del codice senza alcun rischio aggiuntivo.


oggetto composition (vs inheritance) è una questione a parte (e non quello che chiedete, ma condivide il nome). È uno di contenimento per derivare la gerarchia di oggetti in contrapposizione all'ereditarietà diretta.

2

"Bang for the buck": la composizione del suono implica un elevato rapporto di espressività per regola di composizione. Ogni macro introduce le proprie regole di composizione. Ogni struttura dati personalizzata fa lo stesso. Le funzioni, in particolare quelle che utilizzano strutture dati generali, hanno molte meno regole.

Assegnazione e altri effetti collaterali, in particolare la concorrenza di wrt hanno ancora più regole.

2

Pensa a quando scrivi funzioni o metodi. Si crea un gruppo di funzionalità per svolgere un'attività specifica. Quando si lavora in una lingua orientata agli oggetti, si raggruppa il comportamento attorno alle azioni che si ritiene che un'entità distinta nel sistema eseguirà. I programmi funzionali si allontanano da ciò incoraggiando gli autori a raggruppare le funzionalità secondo un'astrazione. Ad esempio, la libreria Clojure Ring comprende un gruppo di astrazioni che coprono il routing nelle applicazioni web.

L'anello è componibile in cui le funzioni che descrivono i percorsi nel sistema (percorsi) possono essere raggruppate in funzioni di ordine superiore (middlewhere). In effetti, Clojure è così dinamico che è possibile (e sei incoraggiato) a creare schemi di percorsi che possono essere creati dinamicamente in fase di runtime. Questa è l'essenza della composabilità, invece di escogitare schemi che risolvono un certo problema ti focalizzi su schemi che generano soluzioni a una certa classe di problemi. Costruttori e generatori di codice sono solo due dei modelli comuni utilizzati nella programmazione funzionale. La programmazione delle funzioni è l'arte dei pattern che generano altri pattern (e così via e così via).

L'idea è quella di risolvere un problema al livello più elementare, quindi creare schemi o gruppi di funzioni di livello più basso che risolvono il problema. Una volta che inizi a vedere i pattern nel livello più basso hai scoperto la composizione. Mentre la gente scopre modelli del secondo ordine in gruppi di funzioni, può iniziare a vedere un terzo livello. E così via ...

1

Nel contesto del clojure, this comment affronta alcuni aspetti della componibilità. In generale, sembra emergere quando le unità del sistema fanno bene una cosa, non richiedono che altre unità capiscano i suoi interni, evitino gli effetti collaterali e accettino e restituiscano le strutture di dati pervasive del sistema. Tutto quanto sopra può essere visto nell'esempio C++ di M2tM.

3

Composizione in programmazione significa assemblare pezzi più grandi di quelli più piccoli.

  • La composizione di funzioni unarie crea una funzione unaria più complessa concatenando quelle più semplici.
  • La composizione dei costrutti del flusso di controllo posiziona i costrutti del flusso di controllo all'interno di altri costrutti del flusso di controllo.
  • La composizione di strutture dati combina strutture dati più semplici in una più complessa.

Idealmente, un'unità composta funziona come un'unità di base e voi come programmatori non è necessario essere consapevoli della differenza. Se le cose non sono all'altezza dell'ideale, se qualcosa non va bene, il tuo programma composto potrebbe non avere il (combinato) comportamento combinato dei suoi singoli pezzi.

Supponiamo di avere un codice C semplice.

void run_with_resource(void) { 
    Resource *r = create_resource(); 
    do_some_work(r); 
    destroy_resource(r); 
}

C facilita il ragionamento compositivo sul flusso di controllo a livello di funzioni.Non devo preoccuparmi di ciò che accade realmente all'interno di do_some_work(); So solo guardando questa piccola funzione che ogni volta che una risorsa viene creata sulla riga 2 con create_resource(), alla fine verrà distrutta dalla riga 4 da destroy_resource().

Beh, non proprio. Cosa succede se create_resource() acquisisce un lock e destroy_resource() lo libera? Quindi devo preoccuparmi se do_some_work acquisisca lo stesso blocco, il che impedirebbe il completamento della funzione. Cosa succede se do_some_work() chiama longjmp() e salta completamente la fine della mia funzione? Finché non so cosa succede in do_some_work(), non sarò in grado di prevedere il flusso di controllo della mia funzione. Non abbiamo più composizionalità: non possiamo più decomporre il programma in parti, ragionare sulle parti in modo indipendente e riportare le nostre conclusioni all'intero programma. Questo rende la progettazione e il debug molto più difficile ed è per questo che alla gente importa se qualcosa si componga bene.

1

componibilità, applicata alle funzioni, significa che le funzioni sono più piccole e ben definita, così facile da integrare in altre funzioni (ho visto questa idea nel libro "la gioia di clojure")

il concetto può applicarsi ad altre cose che dovrebbero essere composte in qualcos'altro.

lo scopo di componibilità è riutilizzo. per esempio, una funzione ben-build (componibili) è più facile da riutilizzare

macro non sono così ben componibili perché non si può passare come parametri

blocco sono stronzate, perché è possibile dare loro davvero dei nomi (definirli bene) o riutilizzarli. basta fare loro inplace

linguaggi imperativi non sono così componibile perché (alcuni di loro, almeno) non hanno chiusure. se vuoi passare la funzionalità come parametro, sei fregato. devi costruire un oggetto e passare quello; diniego qui: questa ultima idea io non sono del tutto convinto è vero, quindi, la ricerca di più prima di prendere per scontato

un'altra idea su linguaggi imperativi è che essi non compongono bene perché esse implicano stato (da wikipedia knowledgebase :) "Programmazione imperativa - descrive il calcolo in termini di istruzioni che modificano lo stato di un programma").

stato non compongono bene perché sebbene in un input sia stato assegnato un "qualcosa" specifico, quel "qualcosa" genera un output in base al suo stato. stato interno diverso, comportamento diverso. e così puoi dire addio a ciò che ti aspetti che accada.

con stato, si dipende molto dalla conoscenza dello stato corrente di un oggetto ... se si desidera prevederne il comportamento. più roba per mantenere nella parte posteriore della vostra mente, meno componibile (ricordate ben definito o "piccolo e semplice", come in "facile da usare"??)

ps: il pensiero di apprendimento clojure , eh? investigando ...? buon per te !: P