Che cos'è questo pattern (anti?) chiamato? (o come descriverlo)

2

Il caso in cui un operatore a livello sorgente descrive effettivamente un'operazione che si svolgerà in qualche punto futuro, thunk l'operatore reale insieme ai suoi operandi.

Non so se questo ha un nome generico, o forse è un elemento di design di livello troppo basso per essere pensato come un "modello", ma comunque è un elemento che viene spesso visto in un numero di situazioni:

  • Se ho capito bene (non un utente C ++ frequente), Boost :: Phoenix fa questo per rappresentare le sue operazioni lambda e pigro, ad es. overloading + per acquisire effettivamente gli argomenti insieme alla funzione reale + per il loro tipo
  • I combinatori di parser in linguaggi funzionali (o qualcosa di simile a Spirit) sembrano operatori sui dati, ma in realtà costruiscono un parser che viene eseguito successivamente
  • Altre EDSL (in lingue con overloading dell'operatore) sembrano fare molto questo, ad es. le librerie di programmazione reattiva funzionale tendono a costruire in modo invisibile le procedure che eseguiranno ciò che l'operazione sembra stia facendo in questo momento
  • (Di nuovo, se ho capito bene) IO in Haskell costruisce un calcolo imperativo di componenti puri che viene poi "eseguito" dalla magia oscura nascosto nel runtime del linguaggio, mentre sembra che stia eseguendo solo le funzioni sul posto
  • Ad un livello più semplice, Greenspunning un mini-Lisp incorporato in un programma C potrebbe essere considerato la stessa cosa: le S-espressioni possono essere costruite con chiamate a funzioni C, per essere eseguite più tardi (quest'ultima probabilmente sarebbe di solito considerato il caso antipattern), ti fa sembrare che stai passando "a lambda" a una funzione C, quando stai davvero passando i dati dell'interprete

Ovviamente non è un antipattern in tutti i casi, in quanto in alcuni di questi è l'unico modo di usare lo strumento in questione (farlo in C potrebbe far guadagnare un pestaggio). Non penso che sia "Greenspunning" o "utilizzando una EDSL" da solo, in quanto può essere più generico e ampiamente applicabile. Non penso che sia il pattern di Interpreter, anche se forse non l'ho capito correttamente: il calcolo viene costruito direttamente eseguendo il codice del linguaggio host (quindi esiste effettivamente in fase di compilazione, ma di solito non è un linguaggio di prima classe costrutto), anziché analizzare una stringa o altri dati caricati in runtime.

In tutti i casi, ciò che sembra accadere è che un'operazione sembra che stia facendo una cosa (non necessariamente in modo molto convincente, se è C e devi scrivere Aggiungi invece di sovraccaricare +, ma stessa idea), mentre in realtà impacchetta quell'azione per un consumo successivo. Qualcosa come un "costruttore di calcolo"? Ma non ho trovato quel termine in uso.

Non ho un problema pratico da risolvere qui; mi disturba solo che sembra esserci un elemento di design comune a cui non riesco a dare un nome.

(domanda originariamente pubblicata su StackOverflow, spostata a mano)

    
posta Leushenko 08.08.2013 - 05:51
fonte

3 risposte

9

Ciò che sembra descrivere è Lazy Evaluation. I calcoli devono essere eseguiti quando il risultato è necessario, piuttosto che quando appare nel codice sorgente.

In Haskell, questo viene fatto nascondendo questi calcoli dietro astrazioni monadiche. In C ++, le astrazioni sono simili, ma più esplicite e parzialmente nascoste dietro operatori e modelli di espressioni sovraccaricati.

Allo stesso modo, il tuo citato esempio di "Greenspunning" di un costrutto simile a Lisp in C non è un antipattern, ma piuttosto una DSL basata su dati ponderata e sembra, in realtà, essere uno dei modelli più comuni di un FFI per interpreti Lisp e Scheme (specialmente in ECL, Chicken, tinyscheme e altri), poiché C non ha i mezzi per costruire e passare funzioni non eseguite come dati.

Noterai che questi pattern non appaiono in lingue che hanno oggetti funzione di prima classe.

    
risposta data 08.08.2013 - 06:40
fonte
6

Questo è chiamato Lazy Evaluation . È un modello abbastanza comune. Infatti, ogni volta che si utilizza l'operatore && o || in un linguaggio simile a C, il secondo operando verrà valutato solo se necessario. Allo stesso modo, in if / then / else , viene valutato solo uno dei rami.

La valutazione lenta ha alcune proprietà interessanti:

In assenza di effetti collaterali, fare qualcosa di pigro non può cambiare il risultato del programma. può , tuttavia, terminare un programma non terminante. Ad esempio, se hai qualcosa del tipo:

False && infiniteLoop

Sotto valutazione stimolante, ciò comporterà un ciclo infinito, ma sotto valutazione pigra, questo restituirà False .

IOW: la valutazione lazy rende possibile scrivere programmi che non possono essere scritti con una valutazione entusiasta. Considera il famoso esempio di sequenza di Fibonacci in Haskell:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

[Che è un cattivo esempio, BTW, perché non ha una complessità algoritmica ottimale.]

Con gli effetti collaterali tuttavia, la valutazione lenta può cambiare l'ordine in cui si verificano effetti collaterali, oppure non si verificano nemmeno affatto .

Per programmi puri e totali, modifiche di valutazione lazy vs. eager niente affatto , diventa semplicemente una scelta di ottimizzazione per il compilatore. (I programmi totali terminano sempre e l'unica differenza tra la valutazione desiderosa e quella pigra è la terminazione, quindi per i programmi totali non c'è differenza.)

    
risposta data 08.08.2013 - 11:23
fonte
1

Questa non è affatto una valutazione pigra. È qualcosa di completamente indipendente.

In Haskell, questo schema di solito si verifica insieme ai combinatori. Ma i combinatori non richiedono di imitare qualche altra forma di calcolo. (Potrei forse chiamarlo mimando una qualche forma di "punire").

    
risposta data 08.01.2019 - 07:00
fonte

Leggi altre domande sui tag