2008-10-13 8 views
6

estoy escribiendo código que es similar a esto:¿Cómo se diseña un enumerador que devuelve (teóricamente) una cantidad infinita de elementos?

public IEnumerable<T> Unfold<T>(this T seed) 
{ 
    while (true) 
    { 
     yield return [next (T)object in custom sequence]; 
    } 
} 

Obviamente, este método nunca va a volver. (El compilador de C# en silencio lo permite, mientras que R # me da la advertencia "Función nunca regresa".)

En términos generales, es que mal diseño para proporcionar un enumerador que devuelve un número infinito de elementos, sin proporcionando una forma de dejar de enumerar?

¿Hay alguna consideración especial para este escenario? Mem? Perf? ¿Otros problemas?

Si siempre proporcionamos una condición de salida, ¿cuáles son las opciones? Por ejemplo:

  • un objeto de tipo T que representa el límite inclusivo o exclusivo
  • un Predicate<T> continue (como se hace TakeWhile)
  • un recuento (como se hace Take)
  • ...

¿Debemos confiar en los usuarios que llaman al Take(...)/TakeWhile(...) después del Unfold(...)? (Tal vez la opción preferida, ya que aprovecha el conocimiento Linq existente.)

Respondería esta pregunta de manera diferente si el código se publicaría en una API pública, ya sea como es (genérico) o como una implementación específica de este patrón?

+0

Por cierto, puede encontrar esta interesante en relación con su pregunta: http://blogs.msdn.com/b/wesdyer/archive/2007/02/13/the-virtues-of-laziness.aspx – joce

Respuesta

7

Siempre y cuando documente claramente que el método nunca terminará de iterar (el método en sí mismo regresa muy rápido, por supuesto) entonces creo que está bien. De hecho, puede hacer que algunos algoritmos sean mucho más nítidos. No creo que haya implicaciones significativas de memoria/perf, aunque si se refiere a un objeto "costoso" dentro de su iterador, esa referencia será capturada.

Siempre hay formas de abusar de las API: siempre y cuando sus documentos estén claros, creo que está bien.

+0

Además de documentación, creo que el nombre del método debería, si es posible, indicar que la enumeración es infinita. Es decir. "IEnumerable GetAllPrimeNumbers()" está bien, pero no estoy seguro acerca de un nombre como Unfold . – Joe

+1

"Unfold" es el nombre de la función de orden superior, que es el inverso de "fold" y, IMO, un nombre válido en un contexto genérico. En cuanto a las implementaciones específicas, sí, es deseable una mejor denominación. –

+0

Por cierto, el método Agregado es la implementación de Linq de la función "plegado", y que yo sepa, no hay una implementación inversa incorporada. Por favor corrígeme si estoy equivocado. –

6

"En términos generales, ¿es malo el desing para proporcionar un enumerador que devuelve una cantidad infinita de artículos, sin suministrar una manera de detener la enumeración?"

El consumidor del código, puede siempre parada enumeración (usando ruptura por ejemplo u otros medios). Si su enumerador devuelve una secuencia infinita, eso no significa que el cliente del enumerador se vea de alguna manera forzado a nunca interrumpir la enumeración, en realidad no puede hacer un enumerador que garantice que un cliente lo enumerará por completo.

¿Hay que confiar en usuarios llamantes Take (...)/TakeWhile (...) después de Despliegue (...)? (Tal vez la opción preferida , ya que aprovecha el conocimiento existente de Linq .)

Sí, siempre que especifique claramente en su documentación que el enumerador regresa y la secuencia infinita y la interrupción de la enumeración es responsabilidad del llamador, todo debería estar bien.

Devolver secuencias infinitas no es una mala idea, los lenguajes de programación funcionales lo han hecho durante mucho tiempo.

2

Estoy de acuerdo con Jon. El compilador transforma su método en una clase que implementa una máquina de estado simple que mantiene una referencia al valor actual (es decir, el valor que se devolverá a través de la propiedad Actual). Usé este enfoque varias veces para simplificar el código. Si documenta claramente el comportamiento del método, debería funcionar bien.

0

No utilizaría un enumerador infinito en una API pública. Los programadores de C#, incluidos yo, están demasiado acostumbrados al ciclo foreach. Esto también sería coherente con .NET Framework; observe cómo los métodos Enumerable.Range y Enumerable.Repeat toman un argumento para limitar el número de elementos en el enumerable. Microsoft optó por usar Enumerable.Repeat ("", 10) en lugar de Enumerable.Repeat ("") .Take (10) para evitar la enumeración infinita y yo cumpliría con sus elecciones de diseño.

+3

Los desarrolladores de C# deberían comenzar a aprender que los enumeradores enumeran las secuencias (finitas o infinitas) y no sobre las listas. Los generadores finitos no son apropiados para todas las clases de problemas, a veces no se puede saber cuántos elementos se necesitan de antemano. –

+3

El propósito de Repetir/Rango es repetir para un número dado de iteraciones, es por eso que el número está allí. Es perfectamente razonable en otros escenarios tener secuencias infinitas. –

Cuestiones relacionadas