Clean up some code smells in D.S.{Promote,Single}.Defun #424
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
While looking at the defunctionalization code recently, I discovered several smelly aspects of the code. This patch does not fix all such code smells (I plan to tackle more of them while tackling #378), but this does pick some of the lower-hanging fruit. Here are the highlights:
The
reverse
function is used an awful lot inD.S.Promote.Defun.defunctionalize
. It's used here, when the inner loop (go
) is first invoked:singletons/src/Data/Singletons/Promote/Defun.hs
Line 299 in 2ec1843
And it's also used here, inside of
go
itself:singletons/src/Data/Singletons/Promote/Defun.hs
Lines 237 to 240 in 2ec1843
This makes my head spin. Moreover, there other some parts of
go
that usem_args
instead of usingreverse m_args
, which causes some odd-looking code to be generated (e.g., generating bothdata BlahSym2 x y :: z ~> Type
andtype instance Apply (BlahSym2 y x) z = BlahSym3 y x z
). None of this is technially wrong, but I find it hard to follow.Luckily, we can get away without needing either use of
reverse
. Instead of processing a single list ofTyVarBndr
s in reverse order inside ofgo
, we can track two lists ofTyVarBndr
s. To see how this works, imagine we are to generate these defunctionalization symbols:We can accomplish this by "cycling" through the
TyVarBndr
s with two lists: one for the arguments and another for the result kind. Or, in code:Notice that at each iteration of
go
, we take the firstTyVarBndr
from the second list and append it to the end of the first. This makesgo
run in quadratic time, but this is not a new precedent, sincego
was quadratic before due to invokingreverse
in each iteration. Given the choice between these two quadratic-time designs, I prefer the new one. I've applied this refactor to thego
functions inD.S.Promote.Defun.defunctionalize
andD.S.Single.Defun.singDefuns
, and left some comments explaining what is going on.The inner loop of
D.S.Promote.Defun.defunctionalize
works by starting fromn - 1
(wheren
is the number of arguments) and iterating until0
is reached. On the flip side, the inner loop ofD.S.Single.Defun.singDefuns
works by starting from0
and iterating untiln - 1
is reached. For the sake of consistency, I have adopted thesingDefuns
convention (start from0
and count up) for both pieces of code.The inner loops of
D.S.Promote.Defun.defunctionalize
andD.S.Single.Defun.singDefuns
are monadic, but they don't need to be. The only monadic things they do are generate unique names, but this can be done more easily outside of the inner loops themselves. This patch refactors the code to do just that, making the inner loop code pure.A consequence of (2) is that
D.S.Promote.Defun.defunctionalize
now generates defunctionalization symbols in the opposite order that it used to. This causes many, many test cases to have different expected output, making the patch appear larger than it actually is.