Con quale frequenza viene utilizzato seq nel codice di produzione di Haskell?


23

Ho una certa esperienza nella scrittura di piccoli strumenti in Haskell e lo trovo molto intuitivo da utilizzare, in particolare per la scrittura di filtri (che utilizzano interact) che elaborano il loro input standard e lo collegano all'output standard.

Di recente ho provato a utilizzare uno di questi filtri su un file che era circa 10 volte più grande del solito e ho riscontrato un Stack space overflowerrore.

Dopo aver letto (ad esempio qui e qui ) ho identificato due linee guida per risparmiare spazio nello stack (esperti Haskeller, per favore correggimi se scrivo qualcosa che non è corretto):

  1. Evitare le chiamate di funzione ricorsive che non sono ricorsive della coda (questo è valido per tutti i linguaggi funzionali che supportano l'ottimizzazione della coda).
  2. Introdurre seqa forzare la valutazione precoce delle sottoespressioni in modo che le espressioni non diventino troppo grandi prima di essere ridotte (questo è specifico per Haskell, o almeno per le lingue che usano la valutazione pigra).

Dopo aver introdotto cinque o sei seqchiamate nel mio codice, il mio strumento funziona di nuovo senza problemi (anche sui dati più grandi). Tuttavia, trovo che il codice originale fosse un po 'più leggibile.

Dato che non sono un programmatore esperto di Haskell, volevo chiedere se introdurre seqin questo modo fosse una pratica comune e quanto spesso si vedrà normalmente seqnel codice di produzione di Haskell. Oppure ci sono tecniche che consentono di evitare di sequsarle troppo spesso e di utilizzare ancora poco spazio nello stack?


1
Ottimizzazioni come quelle che hai descritto rendono quasi sempre il codice un po 'meno elegante.
Robert Harvey,

@Robert Harvey: Esistono tecniche alternative per ridurre l'utilizzo dello stack? Voglio dire, immagino di dover riscrivere le mie funzioni in modo diverso, ma non ho idea se ci siano tecniche consolidate. Il mio primo tentativo è stato quello di utilizzare le funzioni ricorsive della coda, che mi hanno aiutato ma non mi hanno permesso di risolvere completamente il mio problema.
Giorgio,

Risposte:


17

Sfortunatamente ci sono casi in cui si deve usare seqper ottenere un programma efficiente / ben funzionante per dati di grandi dimensioni. Quindi, in molti casi, non puoi farne a meno nel codice di produzione. Puoi trovare maggiori informazioni in Real World Haskell, Capitolo 25. Profilazione e ottimizzazione .

Tuttavia, ci sono possibilità su come evitare l'uso seqdiretto. Questo può rendere il codice più pulito e più robusto. Qualche idea:

  1. Utilizzare condotto , tubi o iteratees anziché interact. È noto che Lazy IO ha problemi con la gestione delle risorse (non solo della memoria) e gli iterati sono progettati esattamente per ovviare a questo. (Suggerirei di evitare l'Io pigro del tutto, non importa quanto siano grandi i tuoi dati - vedi Il problema con l'I / O pigro .)
  2. Invece di utilizzare seqdirettamente utilizzare (o progettare il proprio) combinatori come foldl ' o foldr' o versioni severe di biblioteche (come Data.Map.Strict o Control.Monad.State.Strict ) che sono progettati per rigorosi calcoli.
  3. Usa l' estensione BangPatterns . Permette di sostituire seqcon una corrispondenza rigorosa del modello. dichiarareIn alcuni casi può essere utile anche la rigidi campi di costruzione .
  4. È anche possibile utilizzare le strategie per forzare la valutazione. La libreria di strategie è principalmente indirizzata a calcoli paralleli, ma ha metodi per forzare un valore su WHNF ( rseq) o anche su NF completo ( rdeepseq). Esistono molti metodi di utilità per lavorare con le raccolte, combinare strategie ecc.

+1: grazie per i suggerimenti e i collegamenti utili. Il punto 3 sembra piuttosto interessante (e la soluzione più semplice per me da usare in questo momento). Per quanto riguarda il suggerimento 1, non vedo come evitare l'IO pigro possa migliorare le cose: per quanto ho capito, l'Id pigro dovrebbe essere migliore per un filtro che dovrebbe elaborare un flusso (forse molto lungo) di dati.
Giorgio,

2
@Giorgio Ho aggiunto un link a Haskell Wiki sui problemi con Lazy IO. Con Lazy IO puoi avere delle difficoltà a gestire le risorse. Ad esempio, se non si legge completamente l'input (come a causa di una valutazione pigra), l' handle del file rimane aperto . E se si va a chiudere manualmente l'handle del file, spesso accade che a causa della lettura lenta della valutazione venga posticipato e si chiude l'handle prima di leggere l'intero input. Ed è spesso abbastanza difficile evitare problemi di memoria con IO pigro.
Petr Pudlák,

Di recente ho avuto questo problema e il mio programma stava esaurendo i descrittori di file. Quindi ho sostituito l'IO pigro con l'IO rigoroso usando rigoroso ByteString.
Giorgio,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.