No such thing as a free lunch I’m afraid.
What you would want to write is
for {
(x1, y1) <- xy1
(x2, y2) <- xy2
(x3, y3) <- xy3 if List(x1, x2, x3).combinations(2).forall { case (xi, xj) => abs(xi - xj) <= 1 } &&
List(y1, y2, y3).combinations(2).forall { case (yi, yj) => abs(yi - yj) <= 1 }
} () //already colorized, either yield true and getOrElse false, or, depending on the rest of your branches, integrate them
or really ((x1, y1), (x2, y2), (x3, y3)).transpose.forall(triple => triple.combinations(2).forall{ case (a, b) => abs(a - b) <= 1}
if that were possible, but there are many performance pitfalls there.
What you did is the seemingly right alternative optimized for performance. I don’t expect locally{}
to do anything though – it just introduces a scope for naming I think. It doesn’t look like you’re actually allocating anything within the locally block, so there is nothing to collect there I think. If you did, it already is eligible for collection (though I’m not sure any collectors will actually collect there), but that’s probably the last place I would look for performance gains.
From what I understand, the rub is that you fat-fingered the manual unrolling of List(x1, x2, x3).combinations(2).forall { case (xi, xj) => abs(xi - xj) <= 1 }
That sounds exactly like something I would fat finger too. As it happens, that’s also exactly the slow part.
The alternatives, I think, are either to
- live with the performance drop the abstraction gives you
- live with the risk of fat fingering it and staring at your monitor until you spot the bug (unused warnings could be helpful in these kinds of things, but probably wouldn’t have saved you here either)
- use a metaprogramming technique (code generation or macros)
At the moment, I would recommend against the third option: the snippet really is too small for code generation to be reasonable, scala 2 macros aren’t portable to scala 3 which is on the close horizon, and scala 3 metaprogramming requires scala 3 which doesn’t have a stable release yet.
With 1 being excluded, I’m afraid staring at the monitor until you catch the bug is the only option right now.
When Scala 3 arrives, metaprogramming and inline functions, possibly combined with code generation could offer some neat allocation free transposition options on tuples. You have some statically, compile time known sequence of pairs of indices, forall (ij1, ij2) somepredicate (matrix(ij1), matrix(ij2))
and generating a sequence of && somepredicate(matrix._i1._j1, matrix._i2._j2)
at compile time should be possible.