Quais são as vantagens do next-iterator sobre esse iterador?

8

Não trabalho muito frequentemente com iteradores Java / C # diretamente, mas quando faço isso sempre me pergunto qual foi o motivo para projetar iteradores da maneira "próxima".

Para iniciar, é necessário mover o iterador, a fim de verificar se há alguns dados, você deve verificar se há o próximo elemento.

Um conceito mais atraente para mim é esse iterador - ele "começa" desde o início e você pode verificar esse estado, digamos isValid. Portanto, o loop sobre a coleção inteira ficaria assim:

while (iter.isValid())
{
  println(iter.current());
  iter.next();
}

Como você está aqui para verificar se existe o próximo elemento, você vai para lá e depois verifica se o estado é válido.

YMMV, mas enfim - existe alguma vantagem do próximo iterador (como em Java / C #) sobre esse iterador?

Nota: esta é uma questão conceitual, sobre o design da linguagem.

greenoldman
fonte
questão é para Java ou C # bzoc método isValid não existir em Java
@NiksTyagi É uma questão conceitual que se aplica igualmente a ambos, pois o design proposto é diferente da implementação de qualquer idioma.
Servy
Cada chamada virtual é cara. Seu exemplo tem três chamadas, .net, apenas duas. Pessoalmente, prefiro reduzi-lo a uma ligação.
CodesInChaos

Respostas:

6

Penso que uma vantagem do modelo C # / .NET sobre o MoveNext()modelo Java hasNext()é que o primeiro implica que algum trabalho possa ser feito. O hasNext()método implica uma simples verificação de estado. Mas e se você estiver iterando um fluxo lento e precisar acessar um banco de dados para determinar se há resultados? Nesse caso, a primeira chamada para hasNext()pode ser uma operação longa de bloqueio. A nomeação do hasNext()método pode ser muito enganadora quanto ao que realmente está acontecendo.

Para um exemplo do mundo real, esse problema de design me incomodou ao implementar as APIs de Extensões Reativas (Rx) da Microsoft em Java. Especificamente, para que o iterador criado por IObservable.asIterable()funcione corretamente, o hasNext()método precisará bloquear e aguardar a chegada da próxima notificação de item / erro / conclusão. Isso não é intuitivo: o nome implica uma simples verificação de estado. Compare isso ao design do C #, onde MoveNext()seria necessário bloquear, o que não é um resultado totalmente inesperado quando você está lidando com um fluxo avaliado preguiçosamente.

O que quero dizer é o seguinte: o modelo "next-iterator" é preferível ao modelo "this-iterator" porque o modelo "this-iterator" é frequentemente uma mentira: você pode ser solicitado a buscar previamente o próximo elemento para verificar o estado do iterador. Um desenho que comunique claramente essa possibilidade é preferível, na minha opinião. Sim, as convenções de nomenclatura de Java seguem o modelo "próximo", mas as implicações comportamentais são semelhantes às do modelo "this-iterator".


Esclarecimento: Talvez contrastar Java e C # fosse uma péssima escolha, porque o modelo de Java é enganoso. Considero que a distinção mais importante nos modelos apresentados pelo OP é que o modelo "this-iterator" desacopla a lógica "é válida" da lógica "recuperar atual / próximo elemento", enquanto uma implementação ideal de "próximo iterador" combina essas operações. Eu acho que é apropriado combiná-los porque, em alguns casos, determinar se o estado do iterador é válido requer a pré-busca do próximo elemento , para que a possibilidade seja tornada o mais explícita possível.

Não vejo uma diferença significativa de design entre:

while (i.isValid()) { // do we have an element?  (implied as fast, non-blocking)
    doSomething(i.current()); // retrieve the element (may be slow!)
    i.next();
}
// ...and:
while (i.hasNext()) { // do we have an element?  (implied as fast, non-blocking)
    doSomething(i.next()); // retrieve the element (may be slow!)
}

Mas vejo uma diferença significativa aqui:

while (i.hasNext()) { // do we have an element?  (implied as fast, non-blocking)
    doSomething(i.next()); // retrieve the element (may be slow!)
}
// ...and:
while (i.moveNext()) { // fetch the next element if it exists (may be slow!)
    doSomething(i.current()); // get the element  (implied as fast, non-blocking)
}

Nos exemplos isValid()e hasNext(), a operação implícita como uma verificação de estado rápida e sem bloqueio pode ser de fato uma operação lenta e de bloqueio . No moveNext()exemplo, a maior parte do trabalho está sendo realizada no método que você esperaria, independentemente de estar lidando com um fluxo avaliado avidamente ou preguiçosamente.

Mike Strobel
fonte
1
+0: ​​Acho que o OP tentou comparar Java / C # vs. outro iterador (tanto o C # IEnumrable quanto o Java Iterator apontam para os elementos "antes" quando criados). Meu entendimento da questão é por que não apontar para o primeiro elemento, em vez de comparar C # vs.Java.
Alexei Levenkov 03/02
Obrigado pela resposta, mas C # e Java usam o mesmo modelo (ambos estão focados no próximo elemento).
greenoldman
Os modelos C # e Java definitivamente não são os mesmos. Veja meu exemplo adicionado. O modelo do C # combina as operações de verificação da presença de um elemento com a busca desse elemento. O Java as divide em operações separadas, com a verificação de presença às vezes exigindo a busca do item sob o capô. É realmente muito semelhante ao exemplo do OP: se você ignorar os nomes dos métodos, hasNext()é semelhante a isValid(), e next()é semelhante ao current().
Mike Strobel
Eu quis dizer modelo no sentido de ESTA questão. Ok, eles não são idênticos, mas ambos são baseados no próximo elemento; quando você itera em ambos, você precisa ir para o próximo elemento no início, porque ambos começam antes da coleta real.
greenoldman
Eu mantenho que o modelo Java é muito semelhante ao modelo "this-iterator" na questão de como ele realmente funciona . Afirmo que as implicações comportamentais entre "this-iterator" e "next-iterator" são mais importantes que as convenções de nomenclatura, e o modelo Java tem implicações comportamentais semelhantes ao seu exemplo "this-iterator". Embora minha resposta possa ter se desviado para o território que o OP não havia considerado ao fazer a pergunta, não creio que isso torne minha resposta menos válida.
Mike Strobel
7

Se houver algum cálculo que precise ser feito para calcular cada valor, antes de solicitar o primeiro valor, antes de solicitar o primeiro valor, você poderá adiar o processamento desse primeiro valor além da construção do iterador.

Por exemplo, o iterador pode representar uma consulta que ainda não foi executada. Quando o primeiro item é solicitado, o banco de dados é consultado para obter os resultados. Usando o design proposto, esse cálculo precisaria ser feito na construção do iterador (o que torna mais difícil adiar a execução) ou ao chamar IsValid, nesse caso, é realmente um nome impróprio, pois a chamada IsValidrealmente obteria o próximo valor.

Servy
fonte
Boa resposta. Você atingiu o mesmo ponto que eu, mas me venceu por alguns segundos. Tenha um voto positivo :).
Mike Strobel 03/02
1
Obrigado pela resposta, mas acredito que está incorreta. Para verificar se o iterador é válido, você não precisa obter o elemento real, apenas a posição do iterador. O IOW tem o mesmo impacto que hasNext, apenas seu estado sobre o estado atual, não o futuro. Ou, adotando outra visão - compare isso com os iteradores do C ++, verifique se o estado é válido (current!=end)sem tocar nos dados reais.
greenoldman
PS. Vamos mostrar o seu cenário - eu gostaria de imprimir os resultados, então tenho que ligar hasNextno início, aqui a consulta é executada, os dados são recuperados. No segundo modelo, eu chamo isValid, aqui a consulta é executada. Portanto, o ponto da execução da consulta é o mesmo.
greenoldman
@greenoldman Então, quando ligar para o IsValidque você está realmente fazendo, vai obter o próximo valor. Isso significa que ele está fazendo exatamente a mesma coisa que o iterador existente (ele não possui a corrente válida desde o início, sem a necessidade de chamar um método para obtê-lo), mas apenas tem um nome incorreto.
Servy
1
Exatamente. O modelo "next-iterator" é preferível ao modelo "this-iterator" porque o modelo "this-iterator" é frequentemente uma mentira: pode ser necessário buscar previamente o próximo elemento para verificar o estado do iterador .
Mike Strobel