fantasyland / sweet-fantasies

sweet.js macros for Fantasy Land compatible structures.
34 stars 7 forks source link

Updates macros for Sweet.js 0.6.x #9

Open robotlolita opened 10 years ago

robotlolita commented 10 years ago

This addresses (https://github.com/puffnfresh/sweet-fantasies/issues/8), it's mainly a complete rewrite of the do notation macro. Other macros should probably be dropped now and rewritten using Sweet.js's custom operators (http://sweetjs.org/doc/main/sweet.html#custom-operators), as they make more sense as infix.

This patch also supports having a naked action as the last expression, so:

var a = $do {
  x <- f()
  y <- g()
  h(x, y)
}

Works and gets desugared as expected:

var a = (function() {
  return f().chain(function(x) {
    return g().chain(function(y) {
      return h(x, y)
    })
  })
}())

A couple of things worth noting about this patch:

  1. Actions that aren't bound to an explicit identifier gets bound to _it. This was mainly for simplicity dealing with all the cases, so: $do { a; b } gets desugared to a.chain(_it => b). I'm unsure if this is much of a problem, though this does introduce an implicit new binding in the scope, which might lead to some awkward things(?)
  2. Do blocks are wrapped in a immediately forced thunk. This was again mainly for simplicity with dealing with variable bindings.
  3. The patch makes use of an experimental feature in Sweet.js 0.6.x (http://sweetjs.org/doc/main/sweet.html#custom-pattern-classes-experimental) to abstract over some patterns. This is mainly to allow constructs to be optionally separated with a semicolon, I'm unsure to which extent this is actually useful.
  4. This patch does not support multiple returns. I'm unsure if this was supported before? It should be reasonably doable though, and I'll give it a go once I've got a little bit more of time.
SimonRichardson commented 10 years ago

Nice, you beat me to it :)

robotlolita commented 10 years ago

So, I took a stab at multiple returns, but there's some issues with it. The following code:

$do {
  x <- Identity.of(a)
 someAction()
  y <- return b + x
  return y + 1
}

Gets expanded to:

(function () {
    var $do$op = Identity.of(a);
    var $do$of = $do$op.of || $do$op.constructor.of;
    return $do$op.chain(function (x) {
        return someAction().chain(function (_it) {
            return $do$of(b).chain(function (y) {
                return $do$of(c);
            });
        });
    });
}());

Rather than the simpler:

(function () {
  return Identity.of(a).chain(funciton(x) {
    return someAction()
  }).map(function(_it) {
    return b + x
  }).map(function(y) {
    return y + 1
  })
}())

The meaning is the same, but the generated code is quite a bit more messy. Generating the simpler version should be possible, but would be quite a bit more of work. What do y'all think?

Oh, it also relies on the assumption that the $do block is well typed. If someone writes something like $do { x <- Identity.of(a); y <- Async.of(b); return c } then the behaviour will be different (but in this case, I do guess the behaviour of that whole thing is undefined anyway?)