Ordine di valutazione delle espressioni in Clojure?

4

Attualmente sto imparando Clojure (il mio primo linguaggio di programmazione funzionale), e sono curioso riguardo al suo ordine di valutazione.

Ecco un esempio:

(take 10 (cycle [1 2 3]))

Se l'espressione cycle non è stata avvolta in un'espressione take , ciò causerebbe un errore di memoria, poiché itererà all'infinito attraverso il vettore. Quindi, come fa sapere che Clojure è stato avvolto con un'espressione take ?

(Se sto usando una terminologia errata qui, apprezzerei anche una piccola spinta!)

    
posta joecritch 13.04.2014 - 19:42
fonte

2 risposte

7

So, how does Clojure know that it has been wrapped with a take expression?

Non è così. La pigrizia è meglio compresa in questo modo: non viene mai valutato nulla a meno che non sia assolutamente necessario. La funzione cycle ha la semantica di "ripetere le cose all'infinito", ma ciò non accade realmente finché l'interprete non deve dimostrare a te, l'utente, che lo fa. Se la sequenza dovesse solo produrre un valore, anche infinito, non farebbe nulla e non ci sarebbe modo di notarlo. Ciò che fa inizia il calcolo effettivo è la necessità di consegnare qualcosa al chiamante, in questo caso take 10 . Deve sapere su quali elementi operare, quindi la funzione cycle produce riluttanti 10 valori, ma non di più.

La funzione take , a sua volta, produce solo un valore. Se fosse solo al mondo, potrebbe anche non preoccuparsi di fare nulla, ma è chiamato dal REPL, che deve stampare i valori sul terminale per il suo contratto. In definitiva, la necessità di stampare il primo carattere del risultato è ciò che guida l'intero calcolo. Questo è esattamente l'opposto dell'approccio tradizionale in cui un'espressione interiore viene valutata fino al completamento e allora passata al livello successivo.

    
risposta data 13.04.2014 - 20:10
fonte
7

Il tuo problema qui sembra essere specificamente intorno alle sequenze di Clojure che possono essere infinite.

Le sequenze di Clojure possono essere nulle o implementare un'interfaccia chiamata ISeq, che tra le altre cose ha 2 funzioni particolarmente importanti, first e next .

Considera la seguente classe java

public class LongsFrom implements ISeq{
    private Long _currentNumber;

    public LongsFrom(Long start){
        _currentNumber = start
    }

    public Object first(){
        return _currentNumber;
    }

    public ISeq next(){
        return new LongsFrom(_currentNumber + 1);
    }
}

Rappresenta una sequenza infinita di numeri crescenti che iniziano dove vuoi tu. Mentre continui a chiamare next () su di esso, continui a realizzare sempre più sequenze, tuttavia tutto quello che devi veramente tenere in memoria è qualunque sia l'ultimo oggetto nella sequenza.

Che ciclo fa creare un'istanza di una classe che implementa l'interfaccia ISeq (sa come calcolare la sequenza ma in realtà non lo fa fino a quando non inizierai a chiamare in seguito). Quindi il tuo comando take inizia effettivamente a chiamare first e rest su questa classe per ottenere comunque gran parte della sequenza di cui ha bisogno.

Una classe che esegue effettivamente cycle sarebbe solo leggermente più complicata del mio esempio LongsFrom. La classe del ciclo potrebbe avere 2 variabili private, una che contiene l'elenco delle cose che deve scorrere e quindi un intero per ricordare quale indice è attualmente attivo. per prima cosa restituirebbe l'articolo nell'indice corrente. next restituirebbe un nuovo oggetto ciclo con la stessa lista interna esatta, ma un indice di 1 + currentIndex (a meno che non siamo alla fine della lista, nel qual caso iniziamo di nuovo l'indice a 0)

Il motivo per cui hai problemi di memoria se non hai il comando take è che se non hai il comando take , il repl prova a print la sequenza e l'implementazione predefinita per print è di continuare a chiamare first e rest fino alla fine della sequenza (che non è mai nel caso di cycle )

    
risposta data 13.04.2014 - 20:05
fonte

Leggi altre domande sui tag