Refactoring Maple code

JacquesC's picture

John Fredsted posted some interesting code dealing with permutations, and I suggested a small improvement.  Here, I want to continue the story of that improvement.  First, let us focus one particular line of code:

  posMaps,negMaps := seq(map((perm::list) ->
		(x::list) -> [seq(op(perm[i],x),i=1..nops(perm))]
	,perms),perms in [posPerms,negPerms]);

which uses a lexically scoped procedure to perform the permutations. The first thing to notice is that op(perm[i],x) is really equivalent to x[perm[i]]. Now that we have that perky op gone, we see that the resulting code expression will return unevaluated if x is unknown, unlike op which throws an error message (correctly so!). So now instead of using scoping, we can let Maple actually evaluate the inner perm[i] calls, and use unapply to recover a routine. Putting that together gives us my suggestion:

posMaps,negMaps := seq(map((perm::list) -> unapply([seq(x[perm[i]],i=1..nops(perm))],x),perms), 
perms in [posPerms,negPerms]);

But now we see that we are using a seq over an integer range where we could have simply done

seq(x[p],p in perm)

which is clearer indeed. Now we can really get tricky: we notice the seq builds a list, but perm is a list, so shouldn't we use map? We sure can! Our first try is

map(y -> x[y], perm)

which works. But it still needs a function. Can we get fancy and replace it too? Yes! We can do:

map2(`?[]`, x, map(`[]`,perm))

Ok, that sure was fun. But that last step was too much. The problem is that the inline version of table selection wants a list as a second argument, and that makes things less clear. So let's go back one step, and stare at what we have next:

map((perm::list) -> unapply(map(y->x[y],perm),x),perms)

Can we do anything we that? I don't see anything obvious. Looking at the whole code, there does not seem to be other available 'simplifications'. There are transformations one might do, but none of them seem to be an actual improvement. What is my favourite version? This one:

posMaps,negMaps := seq({seq}(unapply(map2(`?[]`,x,map(`[]`,perm)),x),perm in perms),
perms in [posPerms,negPerms]);

There are lots and lots of loops and function applications going on, but no inline functions in sight. I would not use this exact version in production code, but it is still my favourite.

Comments

more refactoring

While using `?[]` is neat, that it requires a second map to convert the elements to lists is an annoyance.  Consequently, I'd use seq instead.  Also, rather than creating a set of function, I'd create a function that returns a set:

(posMap,negMaps) := seq(unapply({seq([seq(x[i],i=perm)], perm in perms)},x)
                       , perms in [posPerms,negPerms]);

Unapply works nicely here.  An old hack is to use subs with globals (x and S):
 

(posMaps,negMaps) := seq(subs('S'={seq([seq('x'[i],i=perm)], perm in perms)}, x -> S)
                         , perms in [posPerms,negPerms]);
John Fredsted's picture

Preferred version

Thanks, Jacques and Joe, for your sharp observations. I prefer the double map version, so that my code lines would look like:

posMaps,negMaps := seq(
	map(perm -> unapply(map(i -> x[i],perm),x),perms)
,perms in [posPerms,negPerms]);
JacquesC's picture

Success

My main point, and the reason why I put this in a separate blog post, was that there were a lot of possible variants.  And that, past some point, the only thing to choose between the variants was a matter of personal taste.  The version you have chosen is very clean indeed, and may well be the version I might have chosen for production code.

John Fredsted's picture

I agree

I also think that this version with two map's is very clean. I am happy to find out that my aesthetical feeling here agrees with the one of a master like you.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
}