diferencia entre foldLeft y reduceLeft en Scala
functional-programming higher-order-functions (7)
Aprendí la diferencia básica entre foldLeft
y reduceLeft
foldLeft:
- el valor inicial tiene que ser pasado
reduceLeft:
- toma el primer elemento de la colección como valor inicial
- arroja una excepción si la colección está vacía
¿Hay alguna otra diferencia?
¿Alguna razón específica para tener dos métodos con una funcionalidad similar?
Como referencia, reduceLeft
será un error si se aplica a un contenedor vacío con el siguiente error.
java.lang.UnsupportedOperationException: empty.reduceLeft
Retrabajando el código para usar
myList foldLeft(List[String]()) {(a,b) => a+b}
es una opción potencial. Otra opción es usar la variante reduceLeftOption
, que reduceLeftOption
un resultado envuelto por la opción.
myList reduceLeftOption {(a,b) => a+b} match {
case None => // handle no result as necessary
case Some(v) => println(v)
}
De Principios de Programación Funcional en Scala (Martin Odersky):
La función
reduceLeft
se define en términos de una función más general,foldLeft
.
foldLeft
es comoreduceLeft
pero toma un acumuladorz
, como un parámetro adicional, que se devuelve cuando se llama afoldLeft
en una lista vacía:
(List (x1, ..., xn) foldLeft z)(op) = (...(z op x1) op ...) op x
[a diferencia de reduceLeft
, que lanza una excepción cuando se llama en una lista vacía.]
El curso (ver la lección 5.5) proporciona definiciones abstractas de estas funciones, que ilustran sus diferencias, aunque son muy similares en el uso de la coincidencia de patrones y la recursión.
abstract class List[T] { ...
def reduceLeft(op: (T,T)=>T) : T = this match{
case Nil => throw new Error("Nil.reduceLeft")
case x :: xs => (xs foldLeft x)(op)
}
def foldLeft[U](z: U)(op: (U,T)=>U): U = this match{
case Nil => z
case x :: xs => (xs foldLeft op(z, x))(op)
}
}
Tenga en cuenta que foldLeft
devuelve un valor de tipo U
, que no es necesariamente del mismo tipo que List[T]
, pero reduceLeft devuelve un valor del mismo tipo que la lista).
La razón básica por la que ambos están en la biblioteca estándar de Scala es probablemente porque ambos están en la biblioteca estándar Haskell (llamada foldl
y foldl1
). Si no fue reduceLeft
, a menudo se define como un método de conveniencia en diferentes proyectos.
Para entender realmente qué haces con fold / reduce, mira esto: http://wiki.tcl.tk/17983 muy buena explicación. una vez que obtenga el concepto de fold, reduce se combinará con la respuesta anterior: list.tail.foldLeft (list.head) (_)
Pocas cosas que mencionar aquí, antes de dar la respuesta real:
- Tu pregunta no tiene nada que ver con la
left
, sino con la diferencia entre reducir y doblar - La diferencia no es la implementación en absoluto, solo mira las firmas.
- La pregunta no tiene nada que ver con Scala en particular, se trata más bien de los dos conceptos de programación funcional.
De vuelta a tu pregunta:
Aquí está la firma de foldLeft
(también podría haber sido foldRight
para el punto que voy a hacer):
def foldLeft [B] (z: B)(f: (B, A) => B): B
Y aquí está la firma de reduceLeft
(de nuevo, la dirección no importa aquí)
def reduceLeft [B >: A] (f: (B, A) => B): B
Estos dos parecen muy similares y causaron la confusión. reduceLeft
es un caso especial de foldLeft
(que, por cierto, significa que a veces puedes expresar lo mismo al usar cualquiera de ellos).
Cuando llame a reduceLeft
diga en una List[Int]
que literalmente reducirá toda la lista de enteros en un solo valor, que será de tipo Int
(o un supertipo de Int
, por lo tanto [B >: A]
).
Cuando llame a foldLeft
diga en una List[Int]
que foldLeft
toda la lista (imagine enrollar una hoja de papel) en un solo valor, pero este valor no tiene que estar ni siquiera relacionado con Int
(de ahí [B]
).
Aquí hay un ejemplo:
def listWithSum(numbers: List[Int]) = numbers.foldLeft((List[Int](), 0)) {
(resultingTuple, currentInteger) =>
(currentInteger :: resultingTuple._1, currentInteger + resultingTuple._2)
}
Este método toma una List[Int]
y devuelve un Tuple2[List[Int], Int]
o (List[Int] -> Int)
. Calcula la suma y devuelve una tupla con una lista de enteros y su suma. Por cierto, la lista se devuelve al revés, porque usamos foldLeft
lugar de foldRight
.
foldLeft
es más genérico, puede usarlo para producir algo completamente diferente de lo que originalmente reduceLeft
. Mientras que reduceLeft
solo puede producir un resultado final del mismo tipo o súper tipo del tipo de colección. Por ejemplo:
List(1,3,5).foldLeft(0) { _ + _ }
List(1,3,5).foldLeft(List[String]()) { (a, b) => b.toString :: a }
El foldLeft
aplicará el cierre con el último resultado doblado (la primera vez que usa el valor inicial) y el siguiente valor.
reduceLeft
otro lado, reduceLeft
primero combinará dos valores de la lista y los aplicará al cierre. A continuación, combinará el resto de los valores con el resultado acumulado. Ver:
List(1,3,5).reduceLeft { (a, b) => println("a " + a + ", b " + b); a + b }
Si la lista está vacía, foldLeft
puede presentar el valor inicial como un resultado legal. reduceLeft
por otro lado, no tiene un valor legal si no puede encontrar al menos un valor en la lista.
reduceLeft
es solo un método de conveniencia. Es equivalente a
list.tail.foldLeft(list.head)(_)