Escolar Documentos
Profissional Documentos
Cultura Documentos
Nissocapítulo
Imagine que você é o barman de um barzinho que oferece dois produtos: café ex-
presso e suco de laranja. Um cliente entra e senta. Você tem duas opções:
1. Você prepara um espresso, espreme um suco de laranja, leva os dois até o cli-
ente e deixa ele escolher.
2. Você pergunta ao cliente o que ele gostaria de comer, prepara e serve.
Neste caso, existem outras opções. Você poderia preparar ansiosamente um café
expresso e um suco de laranja antes de qualquer cliente entrar. Quando um cli-
ente chegar, pergunte à pessoapara escolher o que querem, servir o que é solici-
tado e imediatamente fazer uma bebida em preparação para o próximo cliente.
Parece loucura? Não tão.
Imagine que você também está oferecendo tortas de maçã e tortas de morango.
Você esperaria um cliente entrar e escolher um tipo de torta para prepará-la? Ou
você prepararia ansiosamente um de cada tipo? Se você escolher ser preguiçoso,
esperaria que o cliente escolhesse, digamos, torta de maçã. Então você preparava
uma torta de maçã, cortava uma fatia e levava para o cliente. Essa seria a aborda-
gem preguiçosa, embora você tivesse preparado ao mesmo tempo as outras fatias
de torta de maçã.
val x: Int = 2 + 3
Aqui, x é imediatamente avaliado como 5 . Como o Kotlin é uma linguagem es-
trita, ele executa a adição imediatamente. Vejamos outro exemplo:
Algumas linguagens são rígidas (como Kotlin e Java) e outras são preguiçosas; al-
guns são rígidos por padrão e opcionalmente preguiçosos, e outros são preguiço-
sos por padrão e opcionalmente rígidos. Kotlin, no entanto, nem sempre é rigo-
roso. Estas são algumas das construções preguiçosas do Kotlin:
Isso te lembra algo? No capítulo 8, você criou uma versão especial da fol-
dLeft funçãoque poderia escapar do cálculo quando um elemento absorvente
(também chamado de zero elemento ) foi encontrado. Aqui, false é um elemento
absorvente para a && operação e true é um elemento absorvente para a || ope-
ração.
Imagine que você deseja simular operadores booleanos com funções. A lista a se-
guir mostra o que você pode fazer.
println(e(verdadeiro, verdadeiro))
println(e(verdadeiro, falso))
println(e(falso, verdadeiro))
println(e(falso, falso))
}
Usar os operadores booleanos fornece uma maneira mais simples de fazer isso,
mas seu objetivo aqui é evitar esses operadores. Você terminou? A execução desse
programa exibe o seguinte resultado no console:
verdadeiro
verdadeiro
verdadeiro
falso
verdadeiro
falso
falso
falso
Até agora tudo bem. Mas agora tente executar o programa mostrado na listagem a
seguir.
verdadeiro
Exceção no encadeamento "principal" java.lang.IllegalStateException
Preguiçaé necessário em muitas ocasiões. Kotlin, de fato, usa laziness para cons-
truções como if...then , loops e try...catch blocos. Sem isso, um cat-
ch bloco, por exemplo, seria avaliado mesmo na ausência de uma exceção.
1. a Delegate classe, que pode ter qualquer nome que você escolher, não pre-
cisa implementar nenhuma interface. Deve declarar e implementar a função
anterior, que será chamada por reflexão.
2. Se você estivesse declarando a var em vez de a val , a Delegate classetam-
bém deve implementar a função correspondente para definir o arquivo
value:operator fun setValue(thisRef: Any?, property:
KProperty<*>, value: Boolean) .
Kotlin também fornece delegados padrão, entre os quais Lazy podem ser usados
para implementar preguiça:
classe Preguiçoso {
operador fun getValue(thisRef: Any?,
propriedade: KProperty<*>): Booleano = ...
}
Infelizmente, isso não é preguiça de verdade, como você pode ver executando este
programa:
verdadeiro
Exceção no encadeamento "principal" java.lang.IllegalStateException
O que está acontecendo aqui é que as funções usadas para inicializar first e
second não são chamadas quando essas referências são declaradas; isso é pre-
guiça normal. Mas as funções são chamadas quando a or funçãorecebe os argu-
mentos first e second em vez de quando eles são realmente necessários, o que
faria com second que nunca fosse chamado porque nunca seria necessário. Isso
não é preguiça detudo!
implementações de preguiça
Você poderia objetar que a preguiça pode ser representada de uma maneira
muito mais simples usando uma função constante. Como você sabe, por já ter
usado esse conceito nos capítulos anteriores, uma função constante é uma função
que recebe qualquer argumento de qualquer tipo e sempre retorna o mesmo va-
lor. Tal função pode ter o seguinte tipo:
() -> Int
Aqui a função retorna um arquivo Int . Para criar um valor inteiro preguiçoso,
você pode escrever
fun or(a: () -> Boolean, b: () -> Boolean): Boolean = if (a()) true else b()
verdadeiro
verdadeiro
Mas há uma diferença muito mais importante com a verdadeira preguiça: se você
usar o valor duas vezes, a função será chamada duas vezes. Calcular o valor é
uma operação demorada. Isso desperdiça tempo do processador. É o que chama-
mos de chamada por avaliação de nome, o que significa que o valor não é avali-
ado antes de ser necessário, mas é avaliado sempre que necessário! Você poderia
obter o mesmo resultado usando uma fun função, mas você teria que fornecer o
tipo de retorno para a função lançando uma exceção porque o Kotlin não pode in-
feri-lo:
Você consegue pensar em uma solução para resolver esse problema e chamar a
função apenas na primeira vez que for necessário? Esse tipo de avaliação é cha-
mado de chamada por necessidade . Se você se lembra do que aprendeu no capí-
tulo 4, sabe que a resposta é memoização.
Exercício 9.1
Dica
Se você fizer o Lazy tipo estender a função () -> A , deve funcionar bem. Isso
não é obrigatório, mas simplificará o uso do tipo. Use a by lazy construção para
memoização e evite a mutação de estado explícita.
Solução
Como você pode ver, esta implementação não faz uso de mutação de estado. A
mutação de estado é abstraída na by lazy construção fornecida pelo Kotlin. No
capítulo 14, você aprenderá a usar a mesma técnica para abstrair o estado mutá-
vel de compartilhamento.
9.10.1 Compondo valores preguiçosos
Mas isso foi trapaça porque você estava confiando na preguiça da if...else ex-
pressão para evitar avaliar o segundo argumento. Imagine uma função compondo
duas strings:
Agora imagine que a aquisição de cada parâmetro é uma tarefa que consome
muitos recursos, e você deseja fornecer o resultado de constructMessage forma
preguiçosa, para que possa ser usado ou não dependendo de uma condição ex-
terna. Se essa condição não for atendida, as strings name e não devem ser adqui-
ridas. Para o exemplo, digamos que a condição é que um número inteiro aleatório
seja par. Veja como deve funcionar: greetings
Agora você tem que refatorar a constructMessage funçãopara que ele use argu-
mentos avaliados preguiçosamente. Você poderia usar o seguinte:
Não haveria nenhum benefício real em usar valores preguiçosos aqui porque eles
seriam avaliados antes de serem concatenados ao chamar a constructMessa-
ge função, mesmo que a condição não tenha sido atendida. O que seria útil é uma
constructMessage função que retornaria um resultado não avaliado, ou seja,
Lazy<String> sem avaliar nenhum dos parâmetros.
Exercício 9.2
Dica
Verifique se cada parte da mensagem é avaliada apenas uma vez, qualquer que
seja o número de chamadas à função.
Solução
Aqui está uma função executando uma concatenação preguiçosa dos dois argu-
mentos, juntamente com o teste para várias avaliações:
Avaliando saudações
Avaliando nome
Olá, Mickey!
Olá, Mickey!
Avaliando nome
Olá, Donald!
Como você pode ver, o greeting argumento é avaliado apenas uma vez, e o na-
me argumentoé avaliada duas vezes (com dois valores diferentes), mesmo que a
função seja chamada três vezes.
A condição deve ser externa , o que significa que não envolve dados preguiçosos.
Caso contrário, os dados teriam que ser avaliados para testar a condição. Também
é possível definir val funções curried preguiçosas. Você aprendeu sobre funções
curried no capítulo 3.
Exercício 9.3
Solução
O resultado é exatamente o mesmo, mas a sintaxe para chamar a função teve que
ser adaptada. Este é um bom exemplo do uso de funções curry. Você pode precisar
cumprimentar muitas pessoas com a mesma mensagem, mas com um nome dife-
rente. Isso poderia então ser implementadoComo
Muitas vezes,você terá uma função trabalhando em alguns valores avaliados e de-
sejará usar essa função com valores não avaliados sem causar avaliação. Afinal,
essa é a essência da programação.
Exercício 9.4
Crie uma função que tome como argumento uma função curried de dois argu-
mentos avaliados e retorne a função correspondente para um argumento não
avaliado que produz o mesmo resultado, mas não avaliado. Dada a função
escreva uma função chamada lift2 que produza a seguinte função (sem avaliar
nenhum argumento):
(Preguiçoso<String>) -> (Preguiçoso<String>) -> Preguiçoso<String>
Dica
Coloque esta função no Lazy objeto complementar para evitar conflito com outra
implementação de lift2 . Como alternativa, você pode declarar essa função no
nível do pacote e alterar seu nome, por exemplo, para liftLazy2 .
Solução
val elevador2: ((String) -> (String) -> String) -> (Lazy<String>) ->
(Preguiçoso<String>) -> Preguiçoso<String> =
onde f é do tipo (String) -> (String) -> String) (a função para levantar)
e ls1 e ls2 são do tipo Lazy<String> :
val elevador2: ((String) -> (String) -> String) -> (Lazy<String>) ->
(Preguiçoso<String>) -> Preguiçoso<String> =
{f->
{ ls1 ->
{ ls2 ->
PENDÊNCIA()
}
}
}
O resto agora é simples. Você tem uma função curried de dois String argumen-
tos e duas instâncias de Lazy<String> . Obtenha os valores e aplique a função.
Como você deseja obter um Lazy<String> , crie uma Lazy instância do
resultado:
val elevador2: ((String) -> (String) -> String) -> (Lazy<String>) ->
(Preguiçoso<String>) -> Preguiçoso<String> =
{f->
{ ls1 ->
{ ls2 ->
Preguiçoso { f(ls1())(ls2()) }
}
}
}
Exercício 9.5
Generalize esta função para trabalhar com qualquer tipo. Desta vez, escreva uma
fun funçãono nível do pacote.
Solução
Comece com a assinatura. Como a função deve funcionar com qualquer tipo, ela
deve ser parametrizada com A, B, C , e o parâmetro (exclusivo) é uma função
do tipo (A) -> (B) -> C . O tipo de retorno é uma função do tipo (Lazy<A>)
-> (Lazy<B>) -> Lazy<C> :
fun <A, B, C> lift2(f: (A) -> (B) -> C): (Preguiçoso<A>) -> (Preguiçoso<B>) -> Preguiçoso<C
Eu estoucerteza de que agora você entende que Lazy é outro contexto computaci-
onal como List , Option , e Result (e muitos outros que você descobrirá mais
tarde), e você está tentado a fazê map - flatMap lo!
Exercício 9.6
Escreva uma map funçãopara aplicar uma função (A) -> B a a Lazy<A> , retor-
nando a Lazy<B> .
Dica
Solução
Tudo o que você precisa fazer é aplicar a função de mapeamento ao valor lento.
Para não acionar a avaliação, envolva-a em um novo Lazy :
Se a condição for válida, este programa imprime o seguinte, mostrando que os da-
dos são avaliados apenas uma vez, mesmo que sejam usados duas vezes:
Avaliando nome
Olá, Mickey!
Olá, Mickey!
Se a condição não for válida, a saída do programa mostra que a mensagem pa-
drão é avaliada apenas uma vez:
Exercício 9.7
Escreva uma flatMap função para aplicar uma função (A) -> Lazy<B> a a
Lazy<A> , retornando a Lazy<B> .
Dica
Solução
Tudo o que você precisa fazer é aplicar a função de mapeamento ao valor lento e
à avaliação do gatilho. Mas como você não quer que isso aconteça até que seja ne-
cessário, embrulhe tudo de novo Lazy :
Avaliando nome
Avaliando saudações
Olá, Mickey!
Olá, Mickey!
Se a condição não for válida, a saída do programa mostra que apenas a mensa-
gem padrão é avaliada e apenasuma vez:
o Lazy O tipo pode ser composto com outros tipos que você desenvolveu nos capí-
tulos anteriores. Uma das operações mais comuns é transformar a
List<Lazy<A>> em a Lazy<List<A>> , para que a lista possa ser composta pre-
guiçosamente com funções de A . Esse tipo de composição deve ser feito sem ava-
liar os dados.
Exercício 9.8
Solução
Mais uma vez, a solução é simples. Você precisa mapear a lista com uma função
avaliando o conteúdo de cada elemento. Mas porque a sequence funçãonão deve
avaliar nada, envolva esta operação em uma nova Lazy instância:
Avaliando nome1
Avaliando nome2
Avaliando nome3
[Mickey, Donald, Pateta, NIL]
[Mickey, Donald, Pateta, NIL]
QuandoSe você estiver trabalhando com suas próprias funções, talvez não tenha
medo de exceções se tiver certeza de que seu código nunca produzirá nenhuma.
Ao lidar com dados não avaliados, no entanto, você corre o risco de ver uma exce-
ção lançada durante a avaliação.
Avaliar um único dado lançando uma exceção não é nada especial. Mas avaliar
uma lista de Lazy<A> é mais problemático. O que você deve fazer se um ele-
mento lançar uma exceção durante a avaliação?
Exercício 9.9
Essa função deve retornar um Result of List<A> que não foi avaliado e que se
tornará a Success<List<A>> se todas as avaliações forem bem-sucedidas ou
Failure<List<A>> caso contrário. Aqui está um exemplo de um caso de uso de
teste:
Avaliando nome1
Avaliando nome2
Avaliando nome3
Sucesso([Mickey, Donald, Pateta, NIL])
Sucesso([Mickey, Donald, Pateta, NIL])
Avaliando nome4
Falha (exceção ao avaliar name4)
Falha (exceção ao avaliar name4)
Solução
import com.fpinkotlin.common.sequence
...
Você também precisa importar explicitamente a sequence função. Isso faz a dis-
tinção com a sequence função definida no nível do pacote. Outra possibilidade é
usar a traverse funçãovocê criou no capítulo 8:
Como você pode ver, compor funções preguiçosamente é uma questão de escre-
ver a composição normal e envolvê-la em uma Lazy instância. Com essa técnica,
você pode compor o que quiser de forma preguiçosa. Não há mágica aqui. Você
sempre pode fazer o mesmo envolvendo qualquer implementação em uma fun-
ção constante:
Mas, em vez de obter o valor de the Lazy e aplicar o efeito, você pode fazer o con-
trário e passar o efeito para o Lazy para que seja aplicado ao valor:
Mas isso não é muito útil se você quiser aplicar condicionalmente um efeito como
o que você fez nos testes anteriores:
O que você precisa fazer é passar à condição um efeito a ser aplicado se a condi-
ção for válida e outro efeito a ser aplicado caso contrário.
Exercício 9.10
Escreva uma forEach função(um método) na Lazy classe, tomando como parâ-
metros uma condição e dois efeitos e aplicando o primeiro efeito ao valor contido
se a condição for true e o segundo efeito caso contrário.
Dica
Para tornar esta função útil, ela deve permitir a avaliação Lazy somente se neces-
sário. Consequentemente, você precisará definir três versões sobrecarregadas da
função.
Solução
fun forEach(condição: Booleana, ifTrue: (A) -> Unidade, ifFalse: (A) -> Unidade) =
if (condição) ifTrue(valor) else ifFalse(valor)
Mas isso não vai funcionar porque o Kotlin avalia os argumentos das funções if-
True e ifFalse assim que eles são recebidos pela forEach função. Não faz dife-
rença se ambos ifTrue e ifFalse usam o valor. Mas muitas vezes, a ifFal-
se condição não o usa, como no seguinte:
Como o ifFalse manipulador não usa o valor, ele não deve ser avaliado. Mas
também pode ser o contrário, o que significa que o valor será usado se a condição
não for válida. Claro, você pode reverter (negar) a condição. Mas você também
deve cuidar do caso em que ambos os manipuladores precisariam avaliar o ar-
quivo Lazy . Uma possível solução é então escrever três versões da função
Esteja ciente, no entanto, de dois problemas. Para permitir que o Kotlin selecione
a função certa, você terá que especificar explicitamente o tipo de cada manipula-
dor, como neste exemplo:
Se você quiser usar o valor padrão para o ifTrue: () -> Unit argumento (ou
seja, não fazer nada), terá que nomear o argumento nolocal de chamada:
Este é um algoritmo para encontrar os primeiros dez primos, mas este algoritmo
não pode ser implementado sem preguiça. Se você não acredita em mim, experi-
mente. Comece com a primeira linha. Se você for rigoroso, primeiro avaliará a
lista de números inteiros positivos. Você nunca terá a oportunidade de ir para a
segunda linha porque a lista de inteiros é infinita e você esgotará a memória dis-
ponível antes de chegar ao final (inexistente).
Claramente, este algoritmo não pode ser implementado sem preguiça, mas você
sabe como substituí-lo por um algoritmo diferente. O algoritmo anterior era funci-
onal. Se quiser encontrar o resultado sem recorrer à preguiça, terá que substituí-
lo por um algoritmo imperativo, como este:
Claro, funciona. Mas que confusão! Primeiro, é uma receita ruim. Você não deve-
ria incrementar o inteiro testado em 2 em vez de 1 para não testar números pa-
res? E por que testar múltiplos de 3, 5 e assim por diante? Mais importante, po-
rém, não expressa a natureza do problema. É apenas uma receita para calcular o
resultado.
Isso não quer dizer que os detalhes da implementação (como não testar números
pares) não sejam importantes para obter um bom desempenho. Mas esses deta-
lhes de implementação devem ser claramente separados da definição do pro-
blema. A descrição imperativa não é uma descrição do problema. É uma descrição
de outro problema dando o mesmo resultado. Uma solução muito mais elegante é
resolver esse tipo de problema com uma estrutura especial: o preguiçosoLista.
Agoraque você sabe como representar dados não avaliados como instâncias de
Lazy , você pode facilmente definir uma estrutura de dados de lista preguiçosa.
Você a chamará de Stream , e ela será semelhante à lista encadeada isolada-
mente desenvolvida no capítulo 5, com algumas diferenças sutis, mas importan-
tes. A listagem a seguir mostra o ponto de partida do seu Stream tipo de dados.
import com.fpinkotlin.common.Result ①
classe selada Stream<out A> { ②
privado
class Cons<out A> (val interna hd: Lazy<A>, ⑦
val interna tl: Lazy<Stream<A>>):
Stream<A>() { ⑧
objeto complementar {
③ A função head retorna um Result<A> para que possa retornar Empty quando cha-
mada em um fluxo vazio.
1
2
3
Isso provavelmente não parece útil. Para criar Stream uma ferramenta valiosa,
você precisará adicionar algumas funções a ela. Mas primeiro, você deve otimizá-
lo um pouco.
Neste exemplo Java, o gerador usa o par (lista, índice) como o estado mutável a
partir do qual o novo valor é gerado. Nesse caso, o gerador depende de um estado
mutável externo. Por outro lado, uma vez que o valor é avaliado (por meio de
uma operação de terminal), o fluxo não pode mais ser usado. Kotlin também ofe-
rece geradores como a Sequence construção, embora, ao contrário de Java, não
manipule processamento paralelo.
Isso é diferente do Stream que você está definindo, no qual os dados não são ava-
liados, mas podem se tornar parcialmente avaliados sem impedir a reutilização
do fluxo. No anteriorPor exemplo, você causou a avaliação dos três primeiros va-
lores do stream, mas isso não impede a reutilização do stream, como você pode
ver no exemplo a seguir:
1
2
3
1
2
3
Kotlin oferece a Sequence construção que permite a reutilização, mas não me-
moriza os valores gerados, portanto não é preguiçosolista também.
Manipulando streams
DentroNo restante deste capítulo, você aprenderá como criar e compor fluxos en-
quanto aproveita ao máximo o fato de que os dados não são avaliados. Mas, para
ver os fluxos, você precisará de uma função para avaliá-los. E para avaliar um
stream, você precisa de uma função para limitar seu comprimento. Você não gos-
taria de avaliar um fluxo infinito, não é?
Exercício 9.11
Crie uma função repeat tomando como argumento uma função do tipo () ->
A e retornando um fluxo de A .
Solução
Não há dificuldade aqui. Você precisa construir (cons) uma chamada preguiçosa
para o argumento da função com uma chamada preguiçosa para a repeat pró-
pria função:
Esta função não causará nenhum problema de pilha porque a chamada para a
repeat função é preguiçosa.
Exercício 9.12
Dica
Exercício 9.13
Dica
Exercício 9.14
Este teste causa um StackOverflowException sem avaliar nada! Você pode con-
sertar isso?
Solução
Neste exemplo, takeAtMost não está causando nenhum problema porque está
sendo preguiçoso; nada será avaliado além do valor 60.001. Por outro lado, a
dropAtMost funçãotem que chamar a si mesmo recursivamente 60.000 vezes
para que o fluxo resultante comece com o 60.001º elemento. Essa recursão ocorre
mesmo que nenhum elemento seja avaliado.
Exercício 9.15
Dica
Exercício 9.16
Até agora, o único fluxo que você poderia criar era um fluxo infinito de números
inteiros consecutivos ou um fluxo de elementos aleatórios. Para fazer a Strea-
m aulaum pouco mais útil, crie uma iterate função pegando uma semente do
tipo A e uma função de A para A e retornando um fluxo infinito de A . Em se-
guida, redefina a from função em termos de iterate .
Solução
A solução consiste em usar a cons funçãopara criar um fluxo seed como cabeça
e uma chamada recursiva preguiçosa para a iterate função usando
f(seed) como cauda:
Embora essa função seja recursiva, ela não explodirá a pilha porque a chamada
recursiva é preguiçosa. Com esta função, você pode redefinir from como
Um uso interessante dessa função é verificar o que é avaliado. Se você criar uma
função com efeitos colaterais como este
Você também pode criar uma iterate função usando a Lazy<A> como semente
se precisar começar com uma semente não avaliada:
Exercício 9.17
Solução
Mais uma vez, você não precisa tornar a função stack-safe porque a chamada re-
cursiva não é avaliada. A Empty implementação retorna this .
Exercício 9.18
Você precisará escrever uma versão corecursiva dessa função para torná-la se-
gura em pilha.
Solução
Como nas funções recursivas anteriores, a solução incluirá uma função na Stre-
am classechamando uma função auxiliar correcursiva segura de pilha no objeto
complementar. Aqui está a função corecursiva no objeto complementar:
Exercício 9.19
Essa função percorria a lista até encontrar um elemento que satisfizesse o predi-
cado p . O resto da lista não foi examinado porque o || operadoré preguiçoso e
não avalia seu segundo argumento se o primeiro for avaliado como true .
Criar uma exists funçãopara Stream . A função deve fazer com que os elemen-
tos sejam avaliados apenas até que a condição seja atendida. Se a condição nunca
for atendida, todos os elementos serão avaliados.
Solução
Você deve torná-lo seguro para pilha. Para escrever uma implementação segura
em pilha, você deve torná-la recursiva. Aqui está uma implementação possível no
objeto complementar:
Dica
Solução
Essa função não é segura em pilha, portanto não deve ser usada para cálculos
como a soma de uma lista com mais de mil inteiros. Você verá, no entanto, que ele
tem muitos casos de uso interessantes.
Exercício 9.21
Solução
Como você pode verificar executando os testes fornecidos no código que acompa-
nha este livro ( https://github.com/pysaumont/fpinkotlin ), esta função não trans-
bordará a pilha, mesmo para fluxos com mais de um milhão de elementos. Isso
porque foldRight não avalia o resultado por si só. A avaliação depende da fun-
ção utilizada para fazer a dobra. Se essa função construir um novo fluxo (como no
caso de takeWhile ), esse fluxo não será avaliado.
Exercício 9.22
Solução
O elemento inicial é um stream vazio não avaliado ( Lazy { Empty } ). Este será
o valor retornado se o stream estiver vazio. A função usada para dobrar o fluxo
ignora o segundo argumento, portanto, na primeira vez em que é aplicada ao
hd elemento, ela retorna Result(a) . Este resultado nunca muda:
Exercício 9.23
Solução
Comece com um Lazy fluxo vazio. A função usada para fazer a dobra será con-
s uma aplicação não avaliada da função ao elemento atual com o resultado atual:
Exercício 9.24
Solução
Novamente, comece com um fluxo vazio não avaliado. A função usada para do-
brar aplica o filtro ao argumento atual. Se o resultado for true , o elemento é
usado para criar um novo fluxo “consertando-o” com o resultado do fluxo atual.
Caso contrário, o resultado do fluxo atual permanece inalterado (a chamada
b() não avalia nenhum elemento):
Essa função avalia os elementos do fluxo até que a primeira correspondência seja
encontrada. Consulte os testes correspondentes no código acompanhante para ob-
ter detalhes.
Exercício 9.25
Dica
Solução
O elemento inicial é o fluxo (não avaliado) que você deseja anexar. A função fol-
ding cria um novo stream adicionando o elemento atual ao resultado atual
usando a cons função:
Exercício 9.26
Novamente, você começa com um fluxo vazio não avaliado. A função é aplicada
ao elemento atual, produzindo um fluxo ao qual o resultado atual é anexado. Isso
tem o efeito de achatar oresultado (transformando a Stream<Stream<B>> em a
Stream<B> ):
import com.fpinkotlin.common.List
Como você pode ver, funções f e p não são funções puras porque elas registram
no console. Isso ajudará você a entender o que está acontecendo. Este programa
imprime o seguinte:
Mapeamento 1
Mapeamento 2
Mapeamento 3
Mapeamento 4
Mapeamento 5
Filtragem 15
Filtragem 12
Filtragem 9
Filtragem 6
Filtragem 3
[6, 12, NIL]
Isso mostra que todos os elementos são processados pela função f , implicando
em uma travessia completa da lista. Em seguida, todos os elementos são processa-
dos por função p , implicando uma segunda travessia completa da lista resultante
da primeira map . Por outro lado, observe o seguinte programa, que usa a Strea-
m em vez de a List :
Esta é a saída:
Mapeamento 1
Filtragem 3
Mapeamento 2
Filtragem 6
Mapeamento 3
Filtragem 9
Mapeamento 4
Filtragem 12
Mapeamento 5
Filtragem 15
15 [6, 12, NIL]
Você pode ver que a travessia do fluxo ocorre apenas uma vez. Primeiro o ele-
mento 1 é mapeado com f , dando 3 . Em seguida, 3 é filtrado (e descartado por-
que não é um número par). Então 2 é mapeado com f , dando 6 , que é filtrado e
guardado para o resultado.
Como você pode ver, a preguiça dos fluxos permite compor as descrições das com-
putações em vez de seus resultados. A avaliação dos elementos é reduzida ao mí-
nimo. O seguinte resultado é obtido se você usar valores não avaliados para cons-
truir o fluxo e uma função de avaliação com registro ao remover a impressão do
resultado:
gerando 1
Mapeamento 1
Filtragem 3
gerando 2
Mapeamento 2
Filtragem 6
Você pode ver que apenas os dois primeiros elementos são avaliados. As demais
avaliações foram resultado da impressão final.
Exercício 9.27
Escreva uma find função que receba um predicado (uma função de A para Boo-
lean ) como parâmetro e retorne um Result<A> . Isso acontecerá Success se
um elemento corresponder ao predicado ou Empty caso contrário.
Dica
Você não deve ter quase nada para escrever. Combine duas das funções que você
escreveu nas seções anteriores.
Solução
Dica
Solução
Exercício 9.29
A iterate função pode ser ainda mais generalizada. Escreva uma unfold fun-
çãoque usa como parâmetros um estado inicial do tipo S e uma função de S para
Result<Pair<A, S>> , e retorna um fluxo de A . Retornar a Result torna possí-
vel indicar se o fluxo deve parar ou continuar.
Usar um estado S significa que a fonte de geração de dados não precisa ser do
mesmo tipo dos dados gerados. Para aplicar esta nova função, escreva novas ver-
sões de fibs e from em termos da unfold função. Aqui está a unfold assina-
tura:
Solução
A nova versão de from usa a semente inteira como o estado inicial e uma função
de Int a Pair<Int, Int> . Aqui o estado é do mesmo tipo que o valor:
Você pode ver como essas implementações de função são compactas e elegantes!
Exercício 9.30
Usar foldRight para implementar várias funções é uma técnica inteligente. In-
felizmente não serve para filter . Se você testar essa função com um predicado
que não corresponda a mais de 1.000 ou 2.000 elementos consecutivos, ela trans-
bordará a pilha. Escreva uma função de pilha segura filter .
Dica
Solução
Como a head função retorna um par, você deve usar o primeiro elemento desse
par como o head elemento do fluxo. Em teoria, você deve usar o elemento certo
do par para qualquer outro acesso. Não fazer isso causaria uma nova avaliação
da cabeça. Mas como você não acessa a cabeça uma segunda vez, apenas a cauda,
você pode usar stream.getTail() em vez de. Isso permite evitar o uso de uma
variável local para referenciar o resultado de stream.head() :
Resumo
Avaliação estrita significa avaliar os valores assim que eles são referenciados.
Avaliação preguiçosa significa avaliar valores somente se e quando forem
necessários.
Algumas linguagens são rígidas e outras preguiçosas. Alguns são preguiçosos
por padrão e opcionalmente rígidos, outros são rígidos por padrão e opcional-
mente preguiçosos.
Kotlin é uma linguagem estrita. É rigoroso em relação aos argumentos da
função.
Embora o Kotlin não seja preguiçoso, você pode usar a by lazy construção
combinada com uma função para implementar a preguiça.
Com preguiça, você pode manipular e compor estruturas de dados infinitas.
Dobras à direita não causam avaliação de fluxo; apenas algumas funções usa-
das para dobrar o fazem.
Usando dobras, você pode compor várias operações de iteração sem resultar
em várias iterações.
Você pode facilmente definir e compor fluxos infinitos.