Estoy intentando asimilar la traverse
función de Data.Traversable
. No puedo ver su sentido. Como vengo de un trasfondo imperativo, ¿alguien puede explicarme en términos de un bucle imperativo? Se agradecería mucho el pseudocódigo. Gracias.
99
Respuestas:
traverse
es lo mismo quefmap
, excepto que también le permite ejecutar efectos mientras reconstruye la estructura de datos.Eche un vistazo al ejemplo de la
Data.Traversable
documentación.La
Functor
instancia deTree
sería:Reconstruye todo el árbol, aplicándose
f
a cada valor.La
Traversable
instancia es casi la misma, excepto que los constructores se llaman en estilo aplicativo. Esto significa que podemos tener efectos (secundarios) al reconstruir el árbol. Aplicativo es casi lo mismo que mónadas, excepto que los efectos no pueden depender de resultados previos. En este ejemplo, significa que no podría hacer algo diferente a la rama derecha de un nodo dependiendo de los resultados de reconstruir la rama izquierda, por ejemplo.Por razones históricas, la
Traversable
clase también contiene una versión monádica detraverse
calledmapM
. Para todos los efectosmapM
es lo mismo quetraverse
: existe como un método separado porqueApplicative
solo más tarde se convirtió en una superclase deMonad
.Si implementara esto en un lenguaje impuro,
fmap
sería lo mismo quetraverse
, ya que no hay forma de prevenir los efectos secundarios. No puede implementarlo como un bucle, ya que debe atravesar su estructura de datos de forma recursiva. Aquí hay un pequeño ejemplo de cómo lo haría en Javascript:Sin embargo, implementarlo de esta manera te limita a los efectos que permite el lenguaje. Si desea el no determinismo (que es la instancia de la lista de modelos aplicativos) y su lenguaje no lo tiene incorporado, no tiene suerte.
fuente
Functor
, la parte que no es paramétrica. El valor del estado enState
, falla enMaybe
yEither
, el número de elementos en[]
y, por supuesto, los efectos secundarios externos arbitrarios enIO
. No me importa como un término genérico (como lasMonoid
funciones que usan "vacío" y "agregar", el concepto es más genérico de lo que sugiere el término al principio) pero es bastante común y sirve bastante bien.ap
dependan de los resultados anteriores. En consecuencia, he reformulado ese comentario.traverse
convierte las cosas dentro de aTraversable
enTraversable
cosas "dentro" de anApplicative
, dada una función que haceApplicative
s de las cosas.Usemos
Maybe
comoApplicative
y enumeremos comoTraversable
. Primero necesitamos la función de transformación:Entonces, si un número es par, obtenemos la mitad (dentro de a
Just
), de lo contrario obtenemosNothing
. Si todo va "bien", se verá así:Pero...
La razón es que la
<*>
función se usa para construir el resultado, y cuando uno de los argumentos esNothing
,Nothing
regresamos.Otro ejemplo:
Esta función genera una lista de longitud
x
con el contenidox
, por ejemplorep 3
=[3,3,3]
. ¿Cuál es el resultadotraverse rep [1..3]
?Obtenemos los resultados parciales de
[1]
,[2,2]
y[3,3,3]
usandorep
. Ahora la semántica de las listas comoApplicatives
es "tomar todas las combinaciones", por ejemplo,(+) <$> [10,20] <*> [3,4]
es[13,14,23,24]
."Todas las combinaciones" de
[1]
y[2,2]
son dos tiempos[1,2]
. Todas las combinaciones de dos tiempos[1,2]
y[3,3,3]
son seis tiempos[1,2,3]
. Entonces tenemos:fuente
fac n = length $ traverse rep [1..n]
liftA2 (,)
que usar la forma más genéricatraverse
.Creo que es más fácil de entender en términos de
sequenceA
, comotraverse
se puede definir de la siguiente manera.sequenceA
secuencia los elementos de una estructura de izquierda a derecha, devolviendo una estructura con la misma forma que contiene los resultados.También puede pensar
sequenceA
en invertir el orden de dos functores, por ejemplo, pasar de una lista de acciones a una acción que devuelve una lista de resultados.Entonces,
traverse
toma algo de estructura y aplicaf
para transformar cada elemento de la estructura en algún aplicativo, luego secuencia los efectos de esos aplicativos de izquierda a derecha, devolviendo una estructura con la misma forma que contiene los resultados.También puedes compararlo con
Foldable
, que define la función relacionadatraverse_
.Entonces puede ver que la diferencia clave entre
Foldable
yTraversable
es que este último le permite preservar la forma de la estructura, mientras que el primero requiere que doble el resultado en algún otro valor.Un ejemplo simple de su uso es usar una lista como estructura transitable, y
IO
como aplicativo:Si bien este ejemplo es bastante aburrido, las cosas se vuelven más interesantes cuando
traverse
se usa en otros tipos de contenedores o con otros aplicativos.fuente
sequenceA . fmap
para listas es equivalente asequence . map
¿no?IO
tipo se puede usar para expresar efectos secundarios; (2)IO
resulta ser una mónada, lo que resulta muy conveniente. Las mónadas no están esencialmente vinculadas a efectos secundarios. También debe tenerse en cuenta que existe un significado de "efecto" que es más amplio que "efecto secundario" en su sentido habitual, uno que incluye cálculos puros. Sobre este último punto, consulte también ¿Qué significa exactamente "eficaz" ?Es algo así
fmap
, excepto que puede ejecutar efectos dentro de la función del mapeador, que también cambia el tipo de resultado.Imagínese una lista de números enteros que representan los ID de usuario en una base de datos:
[1, 2, 3]
. Si desea quefmap
estos ID de usuario sean nombres de usuario, no puede usar un tradicionalfmap
, porque dentro de la función debe acceder a la base de datos para leer los nombres de usuario (lo que requiere un efecto, en este caso, usar laIO
mónada).La firma de
traverse
es:Con
traverse
, puede hacer efectos, por lo tanto, su código para asignar ID de usuario a nombres de usuario se ve así:También hay una función llamada
mapM
:Cualquier uso de
mapM
puede ser reemplazado portraverse
, pero no al revés.mapM
solo funciona para mónadas, mientras quetraverse
es más genérico.Si lo que desea es lograr un efecto y no devolver ningún valor útil, existen
traverse_
ymapM_
versiones de estas funciones, los cuales ignoran el valor de retorno de la función y son un poco más rápido.fuente
traverse
es el bucle. Su implementación depende de la estructura de datos a atravesar. Eso podría ser una lista, árbol,Maybe
,Seq
(influencia), o cualquier cosa que tenga una forma genérica de que se atraviesa a través de algo así como un ciclo for o función recursiva. Una matriz tendría un bucle for, una lista, un bucle while, un árbol o algo recursivo o la combinación de una pila con un bucle while; pero en los lenguajes funcionales no necesita estos engorrosos comandos de bucle: combina la parte interna del bucle (en forma de función) con la estructura de datos de una manera más directa y menos detallada.Con la
Traversable
clase de tipos, probablemente podría escribir sus algoritmos de manera más independiente y versátil. Pero mi experiencia dice que porTraversable
lo general solo se usa para simplemente pegar algoritmos a estructuras de datos existentes. Es bastante bueno no tener que escribir funciones similares para diferentes tipos de datos calificados también.fuente