Si noti che gli autori qui non si riferiscono a nessuna definizione di foldRight nella libreria standard di scala, come quella definita in Elenco. Si riferiscono alla definizione di foldRight che hanno fornito nella sezione 3.4.
La libreria standard scala definisce foldRight in termini di foldLeft invertendo l'elenco (che può essere eseguito nello spazio di stack costante) quindi chiamando foldLeft con gli argomenti della funzione passata invertiti. Questo funziona per gli elenchi, ma non funziona per una struttura che non può essere invertito in modo sicuro, per esempio:
scala> Stream.continually(false)
res0: scala.collection.immutable.Stream[Boolean] = Stream(false, ?)
scala> res0.reverse
java.lang.OutOfMemoryError: GC overhead limit exceeded
Ora lascia pensare a ciò che dovrebbe essere il risultato di questa operazione:
Stream.continually(false).foldRight(true)(_ && _)
La risposta dovrebbe essere falsa, non importa quanti falsi valori sono nel flusso o se è infinita, se li combineremo con una congiunzione, il risultato sarà falso.
Haskell ovviamente ottiene questo senza alcun problema:
Prelude> foldr (&&) True (repeat False)
False
E questo è a causa di due cose importanti: foldr Haskell attraverserà il flusso da sinistra a destra, non da destra a sinistra, e Haskell è pigro da predefinito. Il primo elemento qui, che foldr attraversa effettivamente la lista da sinistra a destra potrebbe sorprendere o confondere alcune persone che pensano a una piega giusta come partendo da destra, ma l'importante caratteristica di una piega a destra non è quale parte finale di una struttura inizia su, ma in quale direzione è l'associatività. Quindi dare una lista [1,2,3,4] e un op nome op
, una piega a sinistra è
((1 op 2) op 3) op 4)
e un diritto fold è
(1 op (2 op (3 op 4)))
Ma l'ordine di valutazione non dovrebbe importa. Quindi, ciò che gli autori hanno fatto qui nel capitolo 3 è di darti una piega che attraversa la lista da sinistra a destra, ma poiché scala è di default severa, non saremo ancora in grado di attraversare il nostro flusso di falsi infiniti, ma alcuni pazienza, ci arriveremo nel capitolo 5 :) Vi darò un'anteprima, guardiamo alla differenza tra foldRight così come è definita nella libreria standard e come è definita nella classe di caratteri Pieghevole in scala:
Ecco l'implementazione della libreria standard Scala:
def foldRight[B](z: B)(op: (A, B) => B): B
Ecco la definizione da scalaz di pieghevole:
def foldRight[B](z: => B)(f: (A, => B) => B): B
La differenza è che le B sono tutti pigri, e ora otteniamo piegare nuovamente nostro flusso infinito, purché diamo una funzione che è sufficientemente pigro nel suo secondo parametro:
scala> Foldable[Stream].foldRight(Stream.continually(false),true)(_ && _)
res0: Boolean = false
'foldr' in Haskell non è ricorsivo in coda, quindi è ancora incline a impilare gli errori di overflow. Ciò che è diverso in Haskell è la semantica di valutazione lenta, che a volte consente a 'foldr' di non valutare * entrambi * gli argomenti dell'operatore di piegatura. Maggiori dettagli qui: http://www.haskell.org/haskellwiki/Stack_overflow –
Inoltre, il metodo 'foldRight' di Scala incorporato non è incline allo stack overflow (più), dal momento che chiama' foldLeft' nella lista invertita, e 'foldLeft' non è nemmeno ricorsivo. https://github.com/scala/scala/blob/v2.11.2/src/library/scala/collection/immutable/List.scala#L396-L397 https://github.com/scala/scala/blob/v2 .11.2/src/library/scala/collection/LinearSeqOptimized.scala # L105-L114 –
Ticket per il vecchio 'foldRight' vecchia Scala: https://issues.scala-lang.org/browse/SI-3295 –