Estou tentando e falhando em grocar a traverse
função de Data.Traversable
. Não consigo ver seu ponto. Visto que venho de um background imperativo, alguém pode me explicar em termos de um loop imperativo? O pseudo-código seria muito apreciado. Obrigado.
99
Respostas:
traverse
é o mesmo quefmap
, exceto que também permite que você execute efeitos enquanto reconstrói a estrutura de dados.Dê uma olhada no exemplo da
Data.Traversable
documentação.A
Functor
instância deTree
seria:Ele reconstrói a árvore inteira, aplicando-se
f
a todos os valores.A
Traversable
instância é quase a mesma, exceto que os construtores são chamados no estilo aplicativo. Isso significa que podemos ter efeitos (colaterais) enquanto reconstruímos a árvore. O Applicative é quase o mesmo que as mônadas, exceto que os efeitos não podem depender dos resultados anteriores. Neste exemplo, isso significa que você não pode fazer algo diferente para o ramo direito de um nó, dependendo dos resultados da reconstrução do ramo esquerdo, por exemplo.Por razões históricas, a
Traversable
classe também contém uma versão monádica detraverse
chamadomapM
. Para todos os efeitos e propósitosmapM
é o mesmo quetraverse
- existe como um método separado porqueApplicative
só mais tarde se tornou uma superclasse deMonad
.Se você implementasse isso em uma linguagem impura,
fmap
seria o mesmo quetraverse
, já que não há como evitar efeitos colaterais. Você não pode implementá-lo como um loop, pois tem que percorrer sua estrutura de dados recursivamente. Aqui está um pequeno exemplo de como eu faria isso em Javascript:Implementá-lo assim limita você aos efeitos que a linguagem permite. Se você quer o não determinismo (que é a instância da lista de modelos Aplicativos) e sua linguagem não o tem embutido, você está sem sorte.
fonte
Functor
, a parte que não é paramétrica. O valor do estado emState
, falha emMaybe
eEither
, o número de elementos em[]
e, claro, efeitos colaterais externos arbitrários emIO
. Eu não ligo para ele como um termo genérico (como asMonoid
funções usando "vazio" e "anexar", o conceito é mais genérico do que o termo sugere no início), mas é bastante comum e serve ao propósito muito bem.ap
depender dos resultados anteriores. Eu reformulei essa observação em conformidade.traverse
transforma as coisas dentro de umaTraversable
em umaTraversable
das coisas "dentro" de umaApplicative
, dada uma função que fazApplicative
s das coisas.Vamos usar
Maybe
comoApplicative
e listar comoTraversable
. Primeiro, precisamos da função de transformação:Portanto, se um número for par, obtemos metade dele (dentro de a
Just
), caso contrário, obtemosNothing
. Se tudo correr "bem", será assim:Mas...
O motivo é que a
<*>
função é usada para construir o resultado, e quando um dos argumentos éNothing
, nós o obtemosNothing
.Outro exemplo:
Esta função gera uma lista de comprimento
x
com o conteúdox
, por exemplorep 3
=[3,3,3]
. Qual é o resultado detraverse rep [1..3]
?Nós obter os resultados parciais de
[1]
,[2,2]
e[3,3,3]
usandorep
. Agora a semântica de listas comoApplicatives
é "tomar todas as combinações", por exemplo,(+) <$> [10,20] <*> [3,4]
é[13,14,23,24]
."Todas as combinações" de
[1]
e[2,2]
são duas vezes[1,2]
. Todas as combinações de duas vezes[1,2]
e[3,3,3]
são seis vezes[1,2,3]
. Então nós temos:fonte
fac n = length $ traverse rep [1..n]
liftA2 (,)
que usar a forma mais genéricatraverse
.Acho que é mais fácil de entender em termos de
sequenceA
, comotraverse
pode ser definido a seguir.sequenceA
sequencia os elementos de uma estrutura da esquerda para a direita, retornando uma estrutura com a mesma forma contendo os resultados.Você também pode pensar
sequenceA
em inverter a ordem de dois functores, por exemplo, ir de uma lista de ações para uma ação que retorna uma lista de resultados.Então
traverse
pega alguma estrutura e se aplicaf
para transformar cada elemento da estrutura em algum aplicativo, então sequencia os efeitos desses aplicativos da esquerda para a direita, retornando uma estrutura com o mesmo formato contendo os resultados.Você também pode compará-lo com
Foldable
, que define a função relacionadatraverse_
.Portanto, você pode ver que a principal diferença entre
Foldable
eTraversable
é que o último permite que você preserve a forma da estrutura, enquanto o primeiro exige que você dobre o resultado em algum outro valor.Um exemplo simples de seu uso é o uso de uma lista como estrutura percorrível e
IO
como aplicativo:Embora este exemplo seja bastante monótono, as coisas ficam mais interessantes quando
traverse
é usado em outros tipos de contêineres ou usando outros aplicativos.fonte
sequenceA . fmap
para listas é equivalente asequence . map
não é?IO
tipo pode ser usado para expressar efeitos colaterais; (2)IO
passa a ser uma mônada, o que acaba sendo muito conveniente. Mônadas não estão essencialmente ligadas a efeitos colaterais. Também deve ser notado que há um significado de “efeito” que é mais amplo do que “efeito colateral” em seu sentido usual - um que inclui cálculos puros. Sobre este último ponto, veja também o que significa exatamente “eficaz” .É mais
fmap
ou menos parecido , exceto que você pode executar efeitos dentro da função do mapeador, que também altera o tipo de resultado.Imagine uma lista de inteiros que representam IDs de usuário em um banco de dados:
[1, 2, 3]
. Se você quiserfmap
esses IDs de usuário para nomes de usuário, não pode usar um tradicionalfmap
, porque dentro da função você precisa acessar o banco de dados para ler os nomes de usuário (o que requer um efeito - neste caso, usando aIO
mônada).A assinatura de
traverse
é:Com o
traverse
, você pode fazer efeitos, portanto, seu código para mapear IDs de usuário para nomes de usuário se parece com:Também existe uma função chamada
mapM
:Qualquer uso de
mapM
pode ser substituído portraverse
, mas não o contrário.mapM
só funciona para mônadas, emboratraverse
seja mais genérico.Se você quiser apenas para conseguir um efeito e não devolver qualquer valor útil, existem
traverse_
emapM_
versões destas funções, sendo que ambos ignorar o valor de retorno da função e são ligeiramente mais rápido.fonte
traverse
é o loop. Sua implementação depende da estrutura de dados a ser percorrida. Isso pode ser uma lista, árvore,Maybe
,Seq
(influência), ou qualquer coisa que tenha uma forma genérica de ser percorrido através de algo como um para-loop ou função recursiva. Um array teria um loop for, uma lista um loop while, uma árvore ou algo recursivo ou a combinação de uma pilha com um loop while; mas em linguagens funcionais você não precisa desses comandos de loop incômodos: você combina a parte interna do loop (na forma de uma função) com a estrutura de dados de uma maneira mais direta e menos detalhada.Com a
Traversable
typeclass, você provavelmente poderia escrever seus algoritmos mais independentes e versáteis. Mas minha experiência diz que issoTraversable
geralmente é usado apenas para colar algoritmos em estruturas de dados existentes. É muito bom não precisar escrever funções semelhantes para diferentes tipos de dados qualificados também.fonte