2016-03-17 30 views
10

Sto cercando di capire l'iteratore. Noto Python documentation considera l'iteratore come un costrutto in stile funzionale. Non lo capisco davvero.Perché l'iteratore è considerato in stile funzionale nella documentazione di Python?

Non è vero che l'iteratore ha uno stato al suo interno. Quindi quando chiami it.__next__(), muti lo stato dell'iteratore. Per quanto ne so, lo stato mutante di un oggetto non è considerato funzionale, dal momento che la programmazione funzionale enfatizza l'immutabilità e la capacità compositiva dell'oggetto/chiusura.

In realtà, il problema si presenta perché voglio scrivere una procedura/funzione Schema che richiede token e restituisce un iteratore.

(define tokens->iterator 
    (lambda ls 
    (lambda() 
     (if (null? ls) 
      '*eoi* 
      (let ((tok (car ls))) 
      (set! ls (cdr ls)) 
      tok))))) 

Avviso devo usare set! di mutare ls, è così che si presenta con questa domanda.

Per usarlo,

(define it (tokens->iterator 1 '+ 2)) 

Per provarlo,

[email protected](guile-user)> (it) 
$2 = 1 
[email protected](guile-user)> (it) 
$3 = + 
[email protected](guile-user)> (it) 
$4 = 2 
[email protected](guile-user)> (it) 
$5 = *eoi* 
[email protected](guile-user)> (it) 
$6 = *eoi* 

Solo per divertimento, ho anche traduco questo per Python:

def tokens_to_iterator(*tup): 
    ls = list(tup) 
    def iterator(): 
     if not ls: 
      return "*eoi*" 
     else: 
      tok = ls.pop(0) 
      return tok 
    return iterator 

Allo stesso modo, il il metodo pop() rimuove e restituisce il primo elemento mediante la mutazione la lista.

Per usarlo,

it = tokens_to_iterator(1, "+", 2) 

per testarlo,

>>> it() 
1 
>>> it() 
'+' 
>>> it() 
2 
>>> it() 
'*eoi*' 
>>> it() 
'*eoi*' 

chiunque può chiarire su questo? A proposito, sto usando Python 3 e Guile Scheme nel caso qualcuno sia interessato a provare gli esempi.

+4

"Funzionale" e "senza stato mutabile" non sono la stessa cosa. – zwol

+0

perché questo stato è nascosto all'interno e inaccessibile dall'esterno; è * incapsulato *. Cambia automaticamente su ogni * prossima * chiamata e la validità dell'oggetto viene preservata. Dal punto di vista dell'osservatore esterno, si comporta secondo alcune * leggi *, è * coerente * e questo non può essere modificato arbitrariamente dall'esterno. questo stato è solo un dettaglio implementativo. –

+0

@zwol C'è anche "senza effetti collaterali". È lo stesso di "mutable-state-free"? Perché "Functional" e "mutable-state-free" non sono la stessa cosa? L'enfasi del linguaggio "funzionale" non è su "mutable-state-free"? Mi sto perdendo sth qui? –

risposta

6

Hai un punto eccellente. Gli iteratori non sono certamente "puramente funzionali", il termine spesso usato per descrivere idiomi che non usano affatto alcuna mutazione. Il termine più ampio "funzionale", tuttavia, è definito in modo più approssimativo per indicare programmi che utilizzano una mutazione relativamente piccola, che fanno uso di funzioni di ordine superiore e di prima classe, e forse la più ampia di tutte, "usa strane astrazioni che non assomiglia a C. "

Penso, ad essere sinceri, che vorrei non gli iteratori di chiamata funzionanti. Cioè: sono d'accordo con te.

+3

"usa strane astrazioni che non assomigliano a C", come monade ... –

3

Lo stile funzionale deve funzionare con elenchi di dati nel loro complesso, piuttosto che una raccolta di valori che è possibile modificare per un capriccio. Per esempio, se si dispone di un elenco di numeri, e si desidera modificare il terzo elemento, l'approccio non funzionale è quello di cambiare direttamente esso:

>>> lst = ["a", "b", "c", "d", "e"] 
>>> lst[3] = "Z" 
>>> lst 
["a", "b", "c", "Z", "e"] 

L'approccio funzionale è quello di scrivere una funzione che prende l'originale sequenza e restituisce una nuova lista con la modifica apportata, lasciando l'originale invariato.

>>> lst = ["a", "b", "c", "d", "e"] 
>>> new_lst = [x if i != 3 else "Z" for (i, x) in enumerate(lst)] 
>>> lst 
["a", "b", "c", "d", "e"] 
>>> new_lst 
["a", "b", "c", "Z", "e"] 

Nessuno dei vostri iteratori è puramente funzionale, perché fanno mantenere lo stato mutabile, anche se trattata come una scatola nera è possibile utilizzarli in modo funzionale in quanto l'utente del iteratore non può incidere direttamente quello stato.

Un iteratore puramente funzionale sarebbe una funzione che prende in ingresso la lista e stato attuale, e restituire un valore e un nuovo stato da passare al successivo richiamo della funzione.

>>> state = 0 
>>> def it(lst, state): 
... if state is None: 
...  return None 
... return lst[state], state + 1 
... 
>>> lst = ["a", "b", "c", "d", "e"] 
>>> value, new_state = it(lst, state) 
>>> value 
'a' 
>>> state, new_state 
(0, 1) 
>>> it(lst, new_state) 
('b', 2) 
>>> state, new_state 
(0, 1) 
+0

Risolto.È difficile dimostrare le tecniche puramente funzionali in Python poiché non ha legami 'let' per nominare temporaneamente un risultato di funzione, che non è proprio lo stesso dell'assegnazione di una variabile. (La grande differenza è che è possibile associare un nome * una volta *, non è possibile modificare la rilegatura in seguito.) – chepner

+0

Vedo, quindi lo stato viene passato in modo esplicito, rendendo il tuo iteratore puramente funzionale, poiché lo stesso 'lst' e stesso 'state', l'iteratore restituirà lo stesso elemento. Posso inoltre sostenere che l'iteratore di Python non è puramente funzionale perché 'it .__ next __()' restituirà elementi diversi con lo stesso input (che in questo caso non è nulla), una prova che l'iteratore mantiene lo stato internamente? –

+0

corretto. Questo è uno dei motivi per cui, dopo aver passato un iteratore a 'itertools.tee', si suppone che si usi solo uno degli iteratori restituiti dalla funzione, non l'iteratore originale stesso. – chepner