Hay muchas listas/tipos de colecciones diferentes en F #.
F # list
tipo. Como dijo Chris, no puede inicializar un valor recursivo de este tipo, porque el tipo no es flojo ni mutable (la inmutabilidad significa que debe crearlo de inmediato y el hecho de que no sea flojo significa que no puede usar F # recursivo valores usando let rec
). Como dijo ssp, podrías usar Reflection para hackearlo, pero ese es probablemente un caso del que no queremos hablar.
Otro tipo es seq
(que en realidad es IEnumerable
) o el tipo de PowerPack LazyList
. Estos son flojos, por lo que puede usar let rec
para crear un valor cíclico. Sin embargo, (hasta donde yo sé) ninguna de las funciones que trabajan con ellos tiene en cuenta las listas cíclicas. Si crea una lista cíclica, simplemente significa que está creando una lista infinita, por lo que el resultado de (por ejemplo) map
será una lista potencialmente infinita.
Aquí es un ejemplo para LazyList
Tipo:
#r "FSharp.PowerPack.dll"
// Valid use of value recursion
let rec ones = LazyList.consDelayed 1 (fun() -> ones)
Seq.take 5 l // Gives [1; 1; 1; 1; 1]
La cuestión es qué tipos de datos se definiría usted mismo.Chris muestra una lista mutable y si escribe operaciones que la modifiquen, afectarán a toda la lista (si la interpreta como una estructura de datos infinita).
También puede definir un tipo de datos perezoso (potencialmente cíclico) e implementar operaciones que manejan ciclos, por lo que cuando crea una lista cíclica y la proyecta en otra lista, creará una lista cíclica como resultado (y no potencialmente estructura de datos infinita).
La declaración de tipo puede tener este aspecto (estoy usando el tipo de objeto, de manera que podemos utilizar la igualdad de referencia en la comprobación de ciclos):
type CyclicListValue<'a> =
Nil | Cons of 'a * Lazy<CyclicList<'a>>
and CyclicList<'a>(value:CyclicListValue<'a>) =
member x.Value = value
El map
siguiente función se encarga de ciclos - si le das una lista cíclica, devolverá una lista recién creada con la misma estructura cíclica:
let map f (cl:CyclicList<_>) =
// 'start' is the first element of the list (used for cycle checking)
// 'l' is the list we're processing
// 'lazyRes' is a function that returns the first cell of the resulting list
// (which is not available on the first call, but can be accessed
// later, because the list is constructed lazily)
let rec mapAux start (l:CyclicList<_>) lazyRes =
match l.Value with
| Nil -> new CyclicList<_>(Nil)
| Cons(v, rest) when rest.Value = start -> lazyRes()
| Cons(v, rest) ->
let value = Cons(f v, lazy mapAux start rest.Value lazyRes)
new CyclicList<_>(value)
let rec res = mapAux cl cl (fun() -> res)
res
Gracias :) (5 más lo que estúpido validador!) – leppie
Puede valer la pena mencionar que esto se puede hacer en OCaml. Incluso puedo citar la única aplicación práctica de estas capacidades en la implementación de cierres recursivos aquí: http://www.ffconsultancy.com/ocaml/benefits/interpreter.html –