There are three different things in my answer:
demonstration of a general method to get rid of a mutable variable
algorithm-specific method that makes it very easy to create a stream
reference to the general method of turning any manufacturer into a stream on demand
First, make your algorithm general at the heart of the enumeration:
let genblocks n = (* base = [1; ... ; n] *) let base = Array.to_list (Array.init n (fun i -> i+1)) in let blocks = ref [] in let rec loop depth block = let iter i = loop (depth - 1) (i :: block) in match depth with | 0 -> blocks := block :: !blocks | _ -> List.iter iter base in loop n []; !blocks
Despite what the code is doing at the moment, it’s a very simple way to get rid of the enumeration: turn any function of type A -> B that uses a mutable type with type C into a function of type A * C -> B * C , which gets the state and returns its changed value - this is what is called the "state monad." Therefore, I simply add an additional blocks parameter to your loop and iter functions, and make it not return unit , but int list list :
let genblocks n = let base = Array.to_list (Array.init n (fun i -> i+1)) in let rec loop depth blocks block = let iter blocks i = loop (depth - 1) blocks (i :: block) in match depth with | 0 -> block :: blocks | _ -> List.fold_left iter blocks base in loop n [] []
Now let's see what exactly this algorithm does:
When called with argument 3 (hardcoded in code 4), these algorithms return all 3-combinations of numbers 1, 2, and 3. Otherwise, it lists all three-digit numbers in the numbering system in base 3 (using numbers from 1 to 3 instead 0 and 2, as usual).
There is a very simple way to list the numbers that you learned in school: move from number to next, just increase (or decrease). In your case, the list starts with a "large" number and goes to a "small" number, so we are going to decrease. With the fact that your base [1; N], not [0; N-1], decrement function written
let decr n block = let rec decr n = function | [] -> raise Exit | 1::rest -> n :: decr n rest | i::rest -> (i - 1) :: rest in try Some (decr n block) with Exit -> None
I made it return No when we reach 0 (on your system, [1; 1; 1 ..]) to easily stop the listing at this point.
decr 3 [3;3;3];; - : int list option = Some [2; 3; 3] # decr 3 [1;2;3];; - : int list option = Some [3; 1; 3] # decr 3 [1;1;1];; - : int list option = None
It is trivial to list all the numbers from this function:
let start n = Array.to_list (Array.make nn) let genblocks n = let rec gen = function | None -> [] | Some curr -> curr :: gen (decr n curr) in gen (Some (start n))
But it is important that the entire state of the generation is stored in only one value, the current number. This way you can easily turn it into a stream:
let genblocks n = let curr = ref (Some (start n)) in Stream.from (fun _ -> match !curr with | None -> None | Some block -> curr := (decr n block); Some block )
Is there a general way to convert manufacturer-driven functions (which accumulate objects in a rhythm that are most natural according to the problem) to consumer-oriented functions (which produce elements one at a time when it is decided by the consumer)? Yes, and I will explain this in the following blog post:
Generators, Iterators, Control and Continuation
The big idea is that you can, thanks to the mechanical, but complex transformations of your code, indicate what the “context” of the producer is, what its current state is, as encoded as a complex mess of values and (which were called in which the conditional branch you are on this moment). This context then turns into a value that you can use as the “numbers” used here to produce a consumer-oriented flow.