Dobrar (função de ordem superior) - Fold (higher-order function)
Na programação funcional , dobrar (também denominado reduzir , acumular , agregar , comprimir ou injetar ) se refere a uma família de funções de ordem superior que analisam uma estrutura de dados recursiva e, por meio do uso de uma determinada operação de combinação, recombina os resultados do processamento recursivo de seu partes constituintes, construindo um valor de retorno. Normalmente, uma dobra é apresentada com uma função de combinação , um nó superior de uma estrutura de dados e, possivelmente, alguns valores padrão a serem usados sob certas condições. A dobra então prossegue combinando elementos da hierarquia da estrutura de dados , usando a função de forma sistemática.
As dobras são de certo modo duplo para se desenrola , que levam uma semente valor e aplicar uma função de corecursively para decidir como construir progressivamente uma estrutura de dados corecursive, ao passo que uma dobra quebras recursivamente que a estrutura para baixo, substituindo-o com os resultados da aplicação de uma função que combina a cada nó em seus valores terminais e os resultados recursivos ( catamorfismo , versus anamorfismo de desdobramentos).
Como transformações estruturais
As dobras podem ser consideradas como uma substituição consistente dos componentes estruturais de uma estrutura de dados por funções e valores. Listas , por exemplo, são construídas em muitas linguagens funcionais a partir de duas primitivas: qualquer lista é uma lista vazia, comumente chamada de nil ( []
), ou é construída prefixando um elemento na frente de outra lista, criando o que é chamado de nó de cons ( ), resultante da aplicação de uma função (escrita como dois pontos em Haskell ). Pode-se ver uma dobra nas listas substituindo o nulo no final da lista por um valor específico e substituindo cada contras por uma função específica. Essas substituições podem ser vistas como um diagrama:
Cons(X1,Cons(X2,Cons(...(Cons(Xn,nil)))))
cons
(:)
Há outra maneira de realizar a transformação estrutural de maneira consistente, com a ordem dos dois elos de cada nó invertida quando alimentada na função de combinação:
Essas imagens ilustram visualmente a dobra direita e esquerda de uma lista . Eles também destacam o fato de que foldr (:) []
é a função de identidade nas listas (uma cópia superficial na linguagem Lisp ), já que substituir cons por cons
e nil por nil
não mudará o resultado. O diagrama de dobra à esquerda sugere uma maneira fácil de reverter uma lista foldl (flip (:)) []
. Observe que os parâmetros a contras devem ser invertidos, porque o elemento a ser adicionado agora é o parâmetro à direita da função de combinação. Outro resultado fácil de ver deste ponto de vista é escrever a função de mapa de ordem superior em termos de foldr
, compondo a função para atuar sobre os elementos cons
, como:
map f = foldr ((:) . f) []
onde o ponto final (.) é um operador que denota a composição da função .
Essa maneira de ver as coisas fornece uma rota simples para projetar funções semelhantes a dobras em outros tipos e estruturas de dados algébricos , como vários tipos de árvores. Um escreve uma função que recursivamente substitui os construtores do tipo de dados com as funções fornecidas e quaisquer valores constantes do tipo com os valores fornecidos. Essa função é geralmente chamada de catamorfismo .
Nas listas
A dobragem da lista [1,2,3,4,5]
com o operador de adição resultaria em 15, a soma dos elementos da lista [1,2,3,4,5]
. Para uma aproximação grosseira, pode-se pensar nessa dobra como a substituição das vírgulas na lista pela operação +, dando 1 + 2 + 3 + 4 + 5
.
No exemplo acima, + é uma operação associativa , de modo que o resultado final será o mesmo, independentemente dos parênteses, embora a forma específica em que é calculado seja diferente. No caso geral de funções binárias não associativas, a ordem em que os elementos são combinados pode influenciar o valor do resultado final. Nas listas, há duas maneiras óbvias de realizar isso: ou combinando o primeiro elemento com o resultado da combinação recursiva do resto (chamado de dobra direita ), ou combinando o resultado da combinação recursiva de todos os elementos, exceto o último, com o último elemento (chamado de dobra esquerda ). Isso corresponde a um operador binário ser associativo à direita ou associativo à esquerda, na terminologia de Haskell ou Prolog . Com uma dobra direita, a soma seria colocada entre parênteses como 1 + (2 + (3 + (4 + 5)))
, enquanto com uma dobra esquerda seria entre parênteses (((1 + 2) + 3) + 4) + 5
.
Na prática, é conveniente e natural ter um valor inicial que no caso de uma dobra à direita é usado quando se chega ao final da lista, e no caso de uma dobra à esquerda é o que é inicialmente combinado com o primeiro elemento de a lista. No exemplo acima, o valor 0 (a identidade aditiva ) seria escolhido como um valor inicial, dando 1 + (2 + (3 + (4 + (5 + 0))))
para a dobra direita e ((((0 + 1) + 2) + 3) + 4) + 5
para a dobra esquerda. Para a multiplicação, a escolha inicial de 0 não iria funcionar: 0 * 1 * 2 * 3 * 4 * 5 = 0
. O elemento de identidade para multiplicação é 1. Isso nos daria o resultado 1 * 1 * 2 * 3 * 4 * 5 = 120 = 5!
.
Dobras lineares vs. em árvore
O uso de um valor inicial é necessário quando a função combinadora f é assimétrica em seus tipos (por exemplo a → b → b
), ou seja, quando o tipo de seu resultado é diferente do tipo dos elementos da lista. Então, um valor inicial deve ser usado, com o mesmo tipo do resultado de f , para que uma cadeia linear de aplicações seja possível. Se ele será orientado à esquerda ou à direita, isso será determinado pelos tipos esperados de seus argumentos pela função de combinação. Se for o segundo argumento que deve ser do mesmo tipo que o resultado, então f pode ser visto como uma operação binária que se associa à direita e vice-versa.
Quando a função é um magma , ou seja, simétrico em seus tipos ( a → a → a
), e o tipo de resultado é o mesmo que o tipo dos elementos da lista, os parênteses podem ser colocados de forma arbitrária, criando assim uma árvore de subexpressões aninhadas, por exemplo ((1 + 2) + (3 + 4)) + 5
,. Se a operação binária f for associativa, este valor será bem definido, ou seja, o mesmo para qualquer parênteses, embora os detalhes operacionais de como ele é calculado sejam diferentes. Isso pode ter um impacto significativo na eficiência se f não for estrito .
Enquanto as dobras lineares são orientadas para o nó e operam de maneira consistente para cada nó de uma lista , as dobras em forma de árvore são orientadas para a lista inteira e operam de maneira consistente em grupos de nós.
Dobras especiais para listas não vazias
Freqüentemente, deseja-se escolher o elemento de identidade da operação f como o valor inicial z . Quando nenhum valor inicial parece apropriado, por exemplo, quando se deseja dobrar a função que calcula o máximo de seus dois parâmetros em uma lista não vazia para obter o elemento máximo da lista, existem variantes de foldr
e foldl
que usam o último e primeiro elemento da lista, respectivamente, como o valor inicial. Em Haskell e em várias outras linguagens, estes são chamados de foldr1
e foldl1
, o 1 fazendo referência à provisão automática de um elemento inicial, e ao fato de que as listas às quais se aplicam devem ter pelo menos um elemento.
Essas dobras usam operação binária de tipo simétrico: os tipos de seus argumentos e de seu resultado devem ser os mesmos. Richard Bird em seu livro de 2010 propõe "uma função de dobra geral em listas não vazias" foldrn
que transforma seu último elemento, ao aplicar uma função de argumento adicional a ele, em um valor do tipo de resultado antes de iniciar a dobra em si, e é, portanto, capaz para usar operação binária com tipo assimétrico como o regular foldr
para produzir um resultado de tipo diferente do tipo de elementos da lista.
Implementação
Dobras lineares
Usando Haskell como exemplo, foldl
e foldr
pode ser formulado em algumas equações.
foldl :: (b -> a -> b) -> b -> [a] -> b
foldl f z [] = z
foldl f z (x:xs) = foldl f (f z x) xs
Se a lista estiver vazia, o resultado é o valor inicial. Caso contrário, dobre o final da lista usando como novo valor inicial o resultado da aplicação de f ao antigo valor inicial e ao primeiro elemento.
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
Se a lista estiver vazia, o resultado será o valor inicial z. Caso contrário, aplique f ao primeiro elemento e o resultado da dobra do resto.
Dobras de árvore
As listas podem ser dobradas em forma de árvore, tanto para listas finitas quanto indefinidamente definidas:
foldt f z [] = z
foldt f z [x] = f x z
foldt f z xs = foldt f z (pairs f xs)
foldi f z [] = z
foldi f z (x:xs) = f x (foldi f z (pairs f xs))
pairs f (x:y:t) = f x y : pairs f t
pairs _ t = t
No caso de foldi
função, para evitar sua avaliação descontrolada em listas indefinidamente definidas, a função nãof
deve sempre exigir o valor de seu segundo argumento, pelo menos não todo ele ou não imediatamente (veja o exemplo abaixo).
Dobras para listas não vazias
foldl1 f [x] = x
foldl1 f (x:y:xs) = foldl1 f (f x y : xs)
foldr1 f [x] = x
foldr1 f (x:xs) = f x (foldr1 f xs)
foldt1 f [x] = x
foldt1 f (x:y:xs) = foldt1 f (f x y : pairs f xs)
foldi1 f [x] = x
foldi1 f (x:xs) = f x (foldi1 f (pairs f xs))
Considerações de ordem de avaliação
Na presença de avaliação preguiçosa , ou não estrita , foldr
retornará imediatamente a aplicação de f para o topo da lista e o caso recursivo de dobrar o resto da lista. Assim, se f é capaz de produzir alguma parte de seu resultado sem referência ao caso recursivo à sua "direita", ou seja, em seu segundo argumento, e o resto do resultado nunca é exigido, então a recursão irá parar (por exemplo, ) . Isso permite que as dobras direitas operem em listas infinitas. Por outro lado, irá chamar a si mesmo imediatamente com novos parâmetros até atingir o final da lista. Essa recursão final pode ser compilada de forma eficiente como um loop, mas não pode lidar com listas infinitas - ela recursará para sempre em um loop infinito .
head == foldr (\a b->a) (error "empty list")
foldl
Tendo atingido o final da lista, uma expressão é efetivamente construída por foldl
de f
aplicativos de aprofundamento à esquerda aninhados , que é então apresentada ao chamador para ser avaliada. Se a função f
se referisse ao seu segundo argumento primeiro aqui, e fosse capaz de produzir alguma parte de seu resultado sem referência ao caso recursivo (aqui, à sua esquerda , isto é, em seu primeiro argumento), então a recursão pararia. Isso significa que, embora foldr
recorra à direita , permite que uma função de combinação preguiçosa inspecione os elementos da lista à esquerda; e, inversamente, embora seja foldl
recorrente à esquerda , permite que uma função de combinação preguiçosa inspecione os elementos da lista à direita, se assim desejar (por exemplo, ).
last == foldl (\a b->b) (error "empty list")
Reverter uma lista também é recursivo na cauda (pode ser implementado usando ). Em listas finitas , isso significa que a dobra à esquerda e reversa podem ser compostas para realizar uma dobra à direita de forma recursiva na cauda (cf. ), com uma modificação na função para que ela inverta a ordem de seus argumentos (isto é, ), tail-recursivamente construindo uma representação da expressão que a dobra à direita construiria. A estranha estrutura de lista intermediária pode ser eliminada com a técnica do estilo continuation-pass ,; da mesma forma, ( só é necessário em linguagens como Haskell com sua ordem invertida de argumentos para a função de combinação de, ao contrário, por exemplo, em Scheme, onde a mesma ordem de argumentos é usada para combinar funções para e ).
rev = foldl (\ys x -> x : ys) []
1+>(2+>(3+>0)) == ((0<+3)<+2)<+1
f
foldr f z == foldl (flip f) z . foldl (flip (:)) []
foldr f z xs == foldl (\k x-> k . f x) id xs z
foldl f z xs == foldr (\x k-> k . flip f x) id xs z
flip
foldl
foldl
foldr
Outro ponto técnico é que, no caso de dobras à esquerda com avaliação preguiçosa, o novo parâmetro inicial não está sendo avaliado antes de ser feita a chamada recursiva. Isso pode levar a estouros de pilha quando se chega ao final da lista e tenta avaliar a expressão potencialmente gigantesca resultante. Por esse motivo, essas linguagens geralmente fornecem uma variante mais restrita da dobra à esquerda, que força a avaliação do parâmetro inicial antes de fazer a chamada recursiva. Em Haskell, esta é a função foldl'
(observe o apóstrofo, pronunciado 'primo') na Data.List
biblioteca (é preciso estar ciente do fato de que forçar um valor construído com um construtor de dados preguiçoso não forçará seus constituintes automaticamente por si só). Combinadas com a recursão da cauda, tais dobras aproximam-se da eficiência dos loops, garantindo uma operação constante no espaço, quando a avaliação preguiçosa do resultado final é impossível ou indesejável.
Exemplos
Usando um interpretador Haskell , as transformações estruturais que as funções de dobra realizam podem ser ilustradas pela construção de uma string:
λ> foldr (\x y -> concat ["(",x,"+",y,")"]) "0" (map show [1..13])
"(1+(2+(3+(4+(5+(6+(7+(8+(9+(10+(11+(12+(13+0)))))))))))))"
λ> foldl (\x y -> concat ["(",x,"+",y,")"]) "0" (map show [1..13])
"(((((((((((((0+1)+2)+3)+4)+5)+6)+7)+8)+9)+10)+11)+12)+13)"
λ> foldt (\x y -> concat ["(",x,"+",y,")"]) "0" (map show [1..13])
"(((((1+2)+(3+4))+((5+6)+(7+8)))+(((9+10)+(11+12))+13))+0)"
λ> foldi (\x y -> concat ["(",x,"+",y,")"]) "0" (map show [1..13])
"(1+((2+3)+(((4+5)+(6+7))+((((8+9)+(10+11))+(12+13))+0))))"
Dobramento infinito em forma de árvore é demonstrado, por exemplo, na produção de primos recursivos por peneira ilimitada de Eratóstenes em Haskell :
primes = 2 : _Y ((3 :) . minus [5,7..] . foldi (\(x:xs) ys -> x : union xs ys) []
. map (\p-> [p*p, p*p+2*p..]))
_Y g = g (_Y g) -- = g . g . g . g . ...
onde a função union
opera em listas ordenadas de maneira local para produzir com eficiência sua união de conjunto e minus
sua diferença de conjunto .
Para listas finitas, por exemplo, a classificação por mesclagem (e sua variedade de remoção de duplicatas nubsort
) pode ser facilmente definida usando dobragem em árvore como
mergesort xs = foldt merge [] [[x] | x <- xs]
nubsort xs = foldt union [] [[x] | x <- xs]
com a função merge
uma variante de preservação de duplicatas union
.
Funciona head
e last
poderia ter sido definido por meio de dobramento como
head = foldr (\x r -> x) (error "head: Empty list")
last = foldl (\a x -> x) (error "last: Empty list")
Em várias línguas
Língua | Dobra esquerda | Dobra direita | Dobra à esquerda sem valor inicial | Dobra direita sem valor inicial | Desdobrar | Notas |
---|---|---|---|---|---|---|
APL |
func⍨/⌽initval,vector
|
func/vector,initval
|
func⍨/⌽vector
|
func/vector
|
||
C # 3.0 |
ienum
|
ienum.Reverse()
|
ienum
|
ienum.Reverse()
|
Agregar é um método de extensão ienum é um método IEnumerable<T> semelhante em todas as linguagens .NET |
|
C ++ |
std::accumulate(
|
std::accumulate(
|
no cabeçalho <numeric> begin , end , rbegin , rend são iteradores func podem ser um ponteiro de função ou um objeto de função |
|||
C ++ 17 |
(initval op ... op pack)
|
(pack op ... op initval)
|
(... op pack)
|
(pack op ...)
|
Expressão de dobra (somente para modelos de função variadic): op é um operador binário (ambos op s devem ser iguais, por exemplo, (std::cout << ... << args) ), pack é um pacote de parâmetros não expandido.
|
|
CFML |
obj.reduce(func,initial)
|
obj.reduce(func)
|
Onde func recebe como argumentos o resultado da operação anterior (ou o initial valor da primeira iteração); o item atual; o índice ou chave do item atual; e uma referência aoobj
|
|||
Clojure |
(reduce func initval list)
|
(reduce func initval (reverse list'))
|
(reduce func list)
|
(reduce func" (reverse list))
|
Veja também clojure.core.reducers / fold | |
Lisp Comum |
(reduce func list :initial-value initval)
|
(reduce func list :from-end t :initial-value initval)
|
(reduce func list)
|
(reduce func list :from-end t)
|
||
Ondulação |
{{TreeNode.default treeNode ...} .to-Iterator}
|
{{TreeNode.default treeNode ...} .reverse}
|
{for {treeNode
|
{for {{treeNode.reverse}
|
também DefaultListModel e HashTable implementam to-Iterator
|
|
D |
reduce!func(initval, list)
|
reduce!func(initval, list
|
reduce!func(list)
|
reduce!func(
|
no módulo std.algorithm
|
|
Elixir |
List.foldl(list, acc, fun)
|
List.foldr(list, acc, fun)
|
Veja a documentação para exemplos de uso | |||
Olmo |
List.foldl(Fun, Accumulator, List)
|
List.foldr(Fun, Accumulator, List)
|
Veja também API de lista [1] | |||
Erlang |
lists:foldl(Fun, Accumulator, List)
|
lists:foldr(Fun, Accumulator, List)
|
||||
F # |
Seq/List.fold func initval list
|
List.foldBack func list initval
|
Seq/List.reduce func list
|
List.reduceBack func list
|
Seq.unfold func initval
|
|
Gosu |
Iterable.fold(f(agg, e))
|
Todos são métodos de extensão na interface Iterable do Java, arrays também são suportados | ||||
Groovy |
list
|
list.reverse()
|
list
|
list.reverse()
|
||
Haskell |
foldl func initval list
|
foldr func initval list
|
foldl1 func list
|
foldr1 func list
|
unfoldr func initval
|
Para foldl, a função de dobramento leva os argumentos na ordem oposta à de foldr. |
Haxe |
Lambda.fold(iterable, func, initval)
|
|||||
J |
verb~/|. initval,array
|
verb/ array,initval
|
verb~/|. array
|
verb/ array
|
u / y aplica a díade u entre os itens de y. "Dicionário J: Inserir" | |
Java 8+ |
stream.reduce
|
stream.reduce
|
||||
JavaScript 1.8 ECMAScript 5 |
array.reduce
|
array.reduce
|
||||
Julia |
foldl(op, itr; [init])
|
foldr(op, itr; [init])
|
foldl(op, itr)
|
foldr(op, itr)
|
||
Kotlin |
Iterable.fold
|
Iterable.foldRight
|
Iterable.reduce(func)
|
Iterable .reduceRight(func)
|
Outras coleções também suportam fold e reduce . Existe também Result.fold(onSuccess, onFailure) , o que reduz a Result<T> (sucesso ou falha) ao tipo de retorno de onSuccess e onFailure .
|
|
LFE |
(lists:foldl func accum list)
|
(lists:foldr func accum list)
|
||||
Logtalk |
fold_left(Closure, Initial, List, Result)
|
fold_right(Closure, Initial, List, Result)
|
Meta-predicados fornecidos pelo objeto de biblioteca padrão meta . As abreviaturas foldl e foldr também pode ser usado. | |||
Bordo |
foldl(func, initval, sequence)
|
foldr(func, initval, sequence)
|
||||
Mathematica |
Fold[func, initval, list]
|
Fold[func, initval, Reverse[list]]
|
Fold[func, list]
|
Fold[func, Reverse[list]]
|
NestWhileList[func,, initval, predicate]
|
Fold sem um valor inicial é compatível com as versões 10.0 e superiores.
|
MATLAB |
fold(@func, list, defaultVal)
|
fold(@func, flip(list), defaultVal)
|
fold(@func, list)
|
fold(@func, flip(list))
|
|
Requer Symbolic Math Toolbox, compatível com R2016b. |
Maxima |
lreduce(func, list, initval)
|
rreduce(func, list, initval)
|
lreduce(func, list)
|
rreduce(func, list)
|
||
Mythryl |
fold_left func initval list
|
fold_right func initval list
|
A função fornecida leva seus argumentos em uma tupla. | |||
OCaml |
List.fold_left func initval list
|
List.fold_right func list initval
|
Base.Sequence.unfold ~init ~f
|
|||
Onça |
{FoldL List Func InitVal}
|
{FoldR List Func InitVal}
|
||||
PARI / GP |
fold( f, A )
|
|||||
Perl |
reduce block initval, list
|
reduce block list
|
no List::Util módulo
|
|||
PHP |
array_reduce(array, func, initval)
|
array_reduce(
|
array_reduce(array, func)
|
array_reduce(
|
Quando initval não é fornecido, NULL é usado, portanto, este não é um foldl1 verdadeiro. Antes do PHP 5.3, initval só pode ser inteiro. "func" é um retorno de chamada . Experimente array_reduce online. | |
Python 2.x |
reduce(func, list, initval)
|
reduce(lambda x,y: func(y,x), reversed(list), initval)
|
reduce(func, list)
|
reduce(lambda x,y: func(y,x), reversed(list))
|
||
Python 3.x |
functools.reduce(func, list, initval)
|
functools.reduce(lambda x,y: func(y,x), reversed(list), initval)
|
functools.reduce(func, list)
|
functools.reduce(lambda x,y: func(y,x), reversed(list))
|
Em funções do módulo . | |
R |
Reduce(func, list, initval)
|
Reduce(func, list, initval, right=TRUE)
|
Reduce(func, list)
|
Reduce(func, list, right=TRUE)
|
R suporta dobra à direita e à esquerda ou à direita com ou sem um valor inicial através dos argumentos right e init para a função Reduce. | |
Rubi |
enum
|
enum.reverse_each
|
enum
|
enum.reverse_each
|
No Ruby 1.8.7+, também pode passar um símbolo representando uma função ao invés de um bloco. enum é uma enumeração Observe que essas implementações de dobras à direita estão erradas para blocos e não comutativos (também o valor inicial é colocado no lado errado). |
|
Ferrugem |
iterator.fold(initval, func)
|
iterator.rev().fold(initval, func)
|
iterator.rev() requer iterator ser um DoubleEndedIterator .
|
|||
Scala |
list.foldLeft(initval)(func) (initval /: list)(func)
|
list.foldRight(initval)(func) (list :\ initval)(func)
|
list.reduceLeft(func)
|
list.reduceRight(func)
|
A sintaxe de dobra simbólica de Scala foi projetada para se assemelhar à árvore inclinada para a esquerda ou para a direita comumente usada para explicar a operação de dobra, mas desde então foi reinterpretada como uma ilustração de um dominó caindo. Os dois pontos vêm de um mecanismo geral de sintaxe Scala por meio do qual o operador infixo aparente é chamado como um método no operando esquerdo com o operando direito passado como um argumento, ou vice-versa se o último caractere do operador for dois pontos, aqui aplicado simetricamente.
Scala também apresenta dobras em forma de árvore usando o método |
|
Esquema R 6 RS |
(fold-left func initval list)
|
(fold-right func initval list)
|
(reduce-left func defaultval list)
|
(reduce-right func defaultval list)
|
srfi / 1 srfi / 43 | |
Conversa fiada |
aCollection inject: aValue into: aBlock
|
aCollection reduce: aBlock
|
ANSI Smalltalk não define #reduce: mas muitas implementações sim. | |||
ML padrão |
foldl func initval list
|
foldr func initval list
|
A função fornecida leva seus argumentos em uma tupla. Para foldl, a função de dobramento leva os argumentos na mesma ordem que para foldr. | |||
Rápido |
array.reduce(initval, func)
|
array.reverse()
|
||||
XPath 3.1 |
array:fold-left(
|
array:fold-right(
|
No XPath 3.1, devido a razões históricas, os tipos array e sequence são incompatíveis - portanto, a necessidade de fold funções separadas para array e parasequence
|
|||
Xtend |
iterable.fold(initval,[func])
|
iterable.reduce[func]
|
Universalidade
Fold é uma função polimórfica . Para qualquer g tendo uma definição
g [] = v
g (x:xs) = f x (g xs)
então g pode ser expresso como
g = foldr f v
Além disso, em uma linguagem preguiçosa com listas infinitas, um combinador de ponto fixo pode ser implementado via dobra, provando que as iterações podem ser reduzidas a dobras:
y f = foldr (\_ -> f) undefined (repeat undefined)
Veja também
- Função agregada
- Operação binária iterada
- Catamorfismo , uma generalização de dobra
- Homomorfismo
- Mapa (função de ordem superior)
- Soma do prefixo
- Tipo de dados recursivo
- Operador de Redução
- Recursão estrutural