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.
Solution 1
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
Solution 2
Having got the job done, I wanted a more elegant solution, so started with this approximation using 'a -> 'a list (lists offer structural pattern matching, whereas seqs 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.
My question
How can we implement lsAll:
- without resorting to constructing an intermediate, explicit tree structure;
- with the desired types (seq, not list);
- using tail recursion (a case for CPS?); and
- without explicit self recursion (e.g. use a fold with accumulator/cps)?
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!