non/spire — pull request #313 by non (2014-10-28 06:44:18+00:00)
Title: Refactor SafeLong to avoid allocating closures.

This commit builds on the strategy introduced in spire.macros.Checked,
but improves it to use a shared var, or a short-circuiting return,
instead of exceptions.

Here are two examples:

def example1(x0: Int, y0: Int): Either[BigInt, Int] =
Checked2.tryOrElse(Right(x0 + y0))(Left(BigInt(x0) + y0)

This (more or less) compiles to:

def example1(x0: Int, y0: Int): Either[BigInt, Int] = {
var ok = true
val z = Right({
val x = x0
val y = y0
val z = x + y
if ((~(x ^ y) & (x ^ z)) < 0) { ok = false }
z
})
if (ok) z else Left(BigInt(x0) + y0)
}

That strategy is nice, since it can be used at any level.
But inside a method definition we might prefer to short-circuit
the overflow case using a return. Here's an example:

def example2(x0: Int, y0: Int): Either[BigInt, Int] =
Checked3.tryOrReturn(Right(x0 + y0))(Left(BigInt(x0) + y0)

This (more or less) compiles to:

def example2(x0: Int, y0: Int): Either[BigInt, Int] = {
def fallback = Left(BigInt(x0) + y0)
Right({
val x = x0
val y = y0
val z = x + y
if ((~(x ^ y) & (x ^ z)) < 0) return fallback
z
})
}

These macros are very preliminary. Here's a short list of known
short-comings:

1. Need to start using fresh names in the macros
2. Should figure out how to name/structure Checked2/Checked3
3. Resolve tensions between usage of encoded/decoded names
4. Consider only assigning to intermediate vals when needed
5. See if we can avoid using resetLocalAttrs.

However, even with these short-comings, the updated SafeLong
should be a lot nicer. All uses of fold/foldWith/map/etc.
have been rewritten in terms of match and macros. We shouldn't
need to allocate any closures.

There are several SafeLong bugfixes that are wrapped up in
this commit too. If we don't want to merge this refactor,
we need to make sure to apply those to master directly.