[MLton-user] Raw conversion of 32 bits into Real32.t

Wesley W. Terpstra wesley at terpstra.ca
Tue Jan 23 03:14:03 PST 2007

Stephen Weeks wrote:
>   val convert =
>      withOne
>      (fn () => Word8Array.tabulate (4, fn _ => 0wx0),
>       fn (a, w) => let
>          val () = PackWord32Little.update (a, 0, w)
>          val r = PackReal32Little.subArr (a, 0)
>       in
>          r
>       end)
> Note that a perfectly valid implementation of One is :
>   structure One: ONE = struct
>      datatype 'a t = T of unit -> 'a
>      val make = T
>      fun use (T f, g) = g (f ())
>   end

The method under discussion is one that is so simple, it should be  
possible to write it directly.

Although it's true that MLton sees through this additional level of  
indirection, I think it's wrong to think that this means the  
complexity is gone. The complexity here is that a programmer looking  
at the method can no longer trivially say what it does. Furthermore,  
there are different costs associated with the first input to withOne.  
As there will only be one implementation of 'One' (forgive the  
pun :-) the different optimization choices may not be simultaneously  
satisfiable. More indirection means more confusion for maintenance  
and more pegs must fit the same hole.

For example, if the 'One' is a shared http connection to google, I  
would be very happy to have it use a thread-safe guard rather than  
reconstruct/reconnect every time. However, when compared to the cost  
of allocating an array (or perhaps even lower) the cost of the guard  
may be prohibitive. Also, programmers who are not writing their code  
for thread-safety (yet) are very unlikely to use ONE. They could  
probably be encouraged to define all variables local that are local.

> So, I don't see much difference between the two "convert" functions,
> except that the "withOne" one has taken care to express the fact that
> the array should be local to each call to convert.  While Wesley's
> certainly expresses the same fact, it is not explicit, and so could be
> intentional or not.

I think that it is common knowledge to programmers that variables  
defined within method scope are race-condition safe. I also think  
that this it is generally good programming style to put a variable  
declaration in as tight a scope as possible.

> Also, by not being explicit it loses the chance for the programmer  
> to optimize things by using different implementations of One.

As I said, I think this is a red herring. Different cost allocation  
methods will prefer different ONE implementations. Certainly you  
could have FastOne, SlowOne, etc. However, the cost of indirection to  
the programmer remains, and is worsened.

> One other point -- MLton should eventually have a primitive for
> directly doing this conversion, eliminating the need to use an array
> entirely.

For something like this, I agree this is the best solution. However,  
I've seen this "temporary array outside of function scope" idiom  
several times. I really think that One shouldn't be necessary for  
this. Stack allocation could be faster than the temporary array  
anyway since the stack is always hot.

On Jan 23, 2007, at 2:58 AM, Matthew Fluet wrote:
> MLton will flatten away some tuples so that they are never  
> allocated on the heap.  I could imagine doing something similar  
> with arrays/vectors of statically known size; it would effectively  
> turn the array/vector into a tuple, which would be flattened.
> Unfortunately, in this particular example, where we are using the  
> array to coerce between Word32.word and Real32.real, it wouldn't  
> really work, since we're necessarily accessing multiple Word8.word  
> elements at a time.

This approach sounds to me far preferable. I am confused by why  
accessing word8 elements would preclude the optimization you described?

Unless this is trivial, of course, I'll shut up and let you work on  
the amd64 port. :-)

More information about the MLton-user mailing list