SML vs Ocaml for ECMA script spec.

Stephen Weeks sweeks at sweeks.com
Tue Oct 24 17:03:07 PDT 2006


> We are looking for a middle ground between precision and
> readability. A direct-style interpreter has the benefit of being
> clearest and requiring no whole-interpreter modifications. Explicit
> continuations or evaluation contexts risk polluting a lot of the
> spec with mostly irrelevant detail.

Quite reasonable.

> I've already written an example implementation of yield in terms of 
> Concurrent ML threads using SML/NJ. I guarantee sequentiality via a 
> simple protocol through a single channel shared between the main thread 
> and the generator thread.

If your primary goal is a clean spec, throwing in Concurrent ML adds
yet another level of semantic complexity.  I appreciate the argument
of keeping the complexity localized, but by using Concurrent ML you've
now added a meta-language semantic complexity that pervades all of the
semantics (the meta-language now has preemptive threads).  I suspect
all that is necessary for ES4 is non-preemptive threads (one example
is MLton.Thread.{new,switch}), which have a much simpler semantics.
My point is that it would be nice to keep the monster in the box in
terms of both the code defining ES4 *and* the explanation of the meta
language.  CML might be too heavyweight.

To be more precise, and to speak in SML code, which is always more
fun, I would think a structure matching the following signature is all
that's needed.

  signature THREAD = sig
  
     type 'a t
  
     val new: ('a -> unit) -> 'a t
     val switch: ('a t -> 'b t * 'b) -> 'a
  
  end

One can implement this signature using continuations, but it is also
easily implementable using MLton.Thread.  The point being that you can
give a simple semantics to the meta-language extended with structure
Thread: THREAD, and then implement structure Thread using whatever
lower-level mechanism the particular SML implementation provides.  And
readers of the ES4 spec don't have to understand the low-level stuff.

Here, e.g., is how I might implement THREAD using a variant of
SMLofNJ.Cont.

  signature CONT = sig
  
     type 'a t
  
     val callcc: ('a t -> 'a) -> 'a
     val throw: 'a t * 'a -> 'b
  
  end
  
  functor ContToThread (structure Cont: CONT
                        val die: string -> 'a): THREAD = struct
  
     structure Thread = struct
        datatype 'a t =
           Dead
         | New of 'a -> unit
         | Paused of 'a Cont.t
     end
  
     datatype z = datatype Thread.t
  
     datatype 'a t = T of 'a Thread.t ref
  
     val func: (unit -> unit) option ref = ref NONE
  
     val start: unit Cont.t option ref = ref NONE
        
     val () = Cont.callcc (fn k => start := SOME k)
  
     val start = valOf (!start)
  
     val () =
        case !func of
            NONE => ()
          | SOME f => let
               datatype t = Exit | Raise of exn
            in
               case (f (); Exit) handle e => Raise e of
                  Exit => die "thread exited"
                | Raise _ => die "thread had unhandled exception"
            end
  
     fun new f = T (ref (New f))
        
     fun switch f = 
        Cont.callcc
        (fn k => let
           val (T r, b) = f (T (ref (Paused k)))
        in
           case !r of
              Dead => die "switch to dead thread"
            | New f => (func := SOME (fn () => f b); Cont.throw (start, ()))
            | Paused k => Cont.throw (k, b)
        end)
  
  end

I haven't used callcc in years, so there are likely errors.  But my
main point is that (I hope) you can improve your spec by containing
the semantic extensions to the meta language.

> Certainly a pre-emptive threading system would make the sequentiality 
> manifest.

I'm not sure what your point is here.

> In fact, I imagine we will be *almost* ML-implementation
> agnostic. Given your warnings, maybe that's naive -- I'm sure you
> have far more experience with this than I. But right now we need to
> move forward, so for the moment I think we're going to go with
> SML/NJ.

Going with SML/NJ is a fine choice.  It's often what I use.  I am
simply saying that you should also compile with another compiler
regularly, and from the start.  It's not that hard.  And if you don't,
you will shoot yourself in the foot, and not find out until much
later, after you have lost a lot of blood.

And if you do settle on MLton for your alternate implementation, you
will get the added benefit of its type error messages, which many
people find significantly better than SML/NJ's.  And since
type-checking is a big part of an SML programmers life :-), this can
be a big win.



More information about the Es4-discuss mailing list