tabulators

Matthew Fluet Matthew Fluet <fluet@CS.Cornell.EDU>
Thu, 24 Jan 2002 13:19:41 -0500 (EST)


> Because S7 can not raise any exceptions and is more powerful than all
> the others, it is, I believe the right interface to use for
> Vector.tabulator.  It also has the nice property that it can be
> implemented without any side-effects, while hiding the type of its
> internal state inside the closure.

Do you really mean without any side-effects (array updates, ref cells,
etc.)?  And the other question is whether you mean inside the Basis
Library (where you've got unsafe array and vector operations) or outside
the Basis Library?

For outside the Basis, I came up with the following with no side effects:

structure VectorTabulator : S7 =
   struct
      type 'a t = 'a vector
      datatype 'a tab = Done of 'a t | More of 'a -> 'a tab
      fun 'a tabulator n =
	 let
	    fun mkTab (l, i) : 'a tab =
	       if i >= n
		  then Done (Vector.fromList (List.rev l))
	       else More (fn x => mkTab (x::l, i - 1))
	 in
	    mkTab ([], 0)
	 end
   end

But, the Vector.fromList seems to somewhat defeat the purpose of a more
general tabulator -- namely, that we don't require building the structure
up from a list.  (Or, maybe I'm missing the point; see my other comments
at the end.)

Outside the Basis, but with side effects, I came up with:

structure VectorTabulator : S7 =
   struct
      type 'a t = 'a vector
      datatype 'a tab = Done of 'a t | More of 'a -> 'a tab
      fun 'a tabulator n =
	 let
	    fun mkTab (a, i) : 'a tab =
	       if i >= n
		  then Done (Array.extract (a, 0, NONE))
	       else More (fn x =>
			  let val _ = Array.update (a, i, x)
			  in mkTab (a, i - 1)
			  end)
	 in
	    if n = 0
	       then Done (Vector.fromList [])
	    else More (fn x =>
		       let val a = Array.array (n, x)
		       in mkTab (a, n - 1)
		       end)
	 end
   end

That's a little better in terms of space, but not much in terms of time;
the Array.extract will still be linear in size of the final vector.

Inside the basis (i.e., when you have access to unsafe operations), you
could do even better, where the Array.extract could be replaced by an
array to vector cast.

> Once we have S7, all the rest are easily implemented using the above
> functors.  The question is, which others should be exported.

Here's a direct conversion from S7 to S1 (rather than 7->6->3->2->1
(introduces 2 refs) or 7->6->5->2->1 (introduces 1 ref)).

functor C7to1 (S: S7): S1 =
   struct
      type 'a t = 'a S.t

      fun tabulator (n, f) =
	 let
	    val r = ref (S.tabulator n)
	    val _ = f (fn x => r := (case !r of
				        S.Done v => tooMany ()
				      | S.More g => g x))
	 in
	    case !r of
	       S.Done v => v
	     | S.More _ => tooFew ()
	 end
   end

A direct S7 to S2 should also be easy.

> S7    tabulator  
> S6	tabulatorDN
> S2	tabulatorFold
> S1	tabulatorSetEach

Those all seem fine to me.  Although, I'm still not quite clear on the
advantages of a tabulator over an unfolder, and how one is supposed to
recognize the right place to use a tabultor.