tldr; go to My Question
I believe that the problem presented here must not be at all new, but I have failed to find any directly corresponding discussion.
Let's say that I have the following function (in order to provide a deterministic substitute for a real-world function having the same structural properties, of type 'a -> 'a seq
):
// I'm a function that looks suspiciously like a tree
let lsExample x =
match x with
| 0 -> seq { 1; 6; 7 }
| 1 -> seq { 2; 3 }
| 3 -> seq { 4; 5 }
| 7 -> seq { 8; 9 }
| _ -> Seq.empty
Now, I wish to have the following:
let lsAll: ('a -> 'a seq) -> 'a -> 'a seq
such that
lsAll lsExample 0
evaluates to
seq { 0 .. 9 }
I have found one long-winded solution to this, and one simple, but still not ideal, solution to a similar problem.
Convert the ls
function to a Rose Tree, then do a pre-order dfs on the tree, as follows:
open FSharpx.Collections
module L = LazyList
module R = Experimental.RoseTree
let rec asRoseTree (ls: 'a -> seq<'a>) (item: 'a) =
let children = ls item
if (Seq.isEmpty children) then
R.singleton item
else
children
|> Seq.map (asRoseTree ls)
|> L.ofSeq
|> R.create item
let lsAll ls =
asRoseTree ls >> R.dfsPre
Having got the job done, I wanted a more elegant solution, so started with this approximation using 'a -> 'a list
(list
s offer structural pattern matching, whereas seq
s don't... I hope no one ever uses this implementation):
let rec lsAll' (ls: 'a -> 'a list) (xs: 'a list) =
match xs with
| [] -> []
| [x] -> lsAll' ls (ls x) |> List.append [x]
| x :: tail -> lsAll' ls tail |> List.append (lsAll' ls [x])
let lsAll ls x = lsAll' ls [x]
I then got stumped trying to make this tail-recursive, even without the extra inconvenience of switching back to seq.
How can we implement lsAll
:
Aside: Having got the job done and written this question up, I'm now thinking that getting the input function into a tree structure might not be a waste at all, and I should have made better use of it. That said, I'm still too curious to give up on this quest!
You can do this very nicely using F# sequence expressions and the yield
and yield!
constructs:
let rec lsAll ls x = seq {
yield x
for c in ls x do
yield! lsAll ls c }
lsAll lsExample 0
A sequence expression seq { .. }
is a code block that generates a sequence. Inside this, you can use yield
to add a single element to the sequence but also yield!
to add all elements of some other sequence. Here, you can do this to include all values produced by a recursive call.
You could combine this with the approach in your solution 2 too:
let rec lsAll ls xs = seq {
match xs with
| [] -> ()
| x::xs ->
yield x
yield! lsAll ls (ls x)
yield! lsAll ls xs }
This requires lsAll
to return a list - you could insert List.ofSeq
on the line before the last, but I think it's probably best to leave this to the user. However, you can now turn this into tail-recursive version by using CPS where the continuation is "sequence of values to be produced after the current one is done":
let rec lsAll ls xs cont = seq {
match xs with
| [] -> yield! cont
| x::xs ->
yield x
yield! lsAll ls (ls x) (lsAll ls xs cont) }
lsAll (lsExample >> List.ofSeq) [0] Seq.empty
If I give this an infinite tree, it does not actually StackOverflow, but keeps allocating more and more memory, so I guess it works!