disnet/contracts.coffee — Issue #17: following coffee script fails, while I think it should succeed
Author: d1m0
Created: 2011-10-18 23:37:45+00:00
State: open

This coffee script fails, even though I think it should succeed:

negate :: (Num or Bool) -> !(r)->typeof r == typeof $1
negate = (x) ->
if typeof x is 'number'
0 - x
else
!x
negate = negate.use()

x :: Num
x = negate 100
y :: Bool
y = negate true

If we modify the contract, so that it actually prints out the value of $1 like this:

negate :: (Num or Bool) -> !(r)-> alert $1; typeof r == typeof $1
negate = (x) ->
if typeof x is 'number'
0 - x
else
!x

negate = negate.use()

x :: Num
x = negate 100
y :: Bool
y = negate true

You will notice that it alerts "100" twice, instead of "true" the second time. I think this is cause by these lines in contracts.js:

function fun(dom, rng, options) {
....
c = new Contract(contractName, "fun", function(f, pos, neg, parentKs, stack) {
....
var makeHandler = function(dom, rng, options) {
return function functionHandler() {
....
if(typeof rng === "function") { <<-- On the first check of the contract rng is still a function
// send the arguments to the dependent range
rng = rng.apply(this, args); <<--- here we reassign it to an object. Note that rng is a closure variable
}

When we first check the contract, rng contains a function, so we go into the if statement, and apply it. This however returns a contract which is an object, containing a closure. Unfortunately, that closure enclosed the arguments that were passed to the negate, on the invocation we are checking (i.e. 100 in the example above).

The next time we invoke the negate, rng is no longer a function (since it was a variable defined in the closure returned by makeHandler, it value was remembered between invocations), therefore we don't go in the if statement, and we just use the closure defined the pervious time, which contains the arguments from the last invocation.

Don't know what the correct solution is here, but the following worked ok for me, for the above and a couple of other examples:

var frng;
...
if(typeof rng === "function") {
// send the arguments to the dependent range
frng = rng.apply(this, args);
} else {
frng = rng;
}
// And use frng instead of rng for the checks below

Hope this helps
~Dimo