Simplify type signatures and make function implementations more strait-forward

This results in more code duplication, but the resulting type signatures are much simpler and the implementations are far easier to understand. It should be easier for llvm to optimise things too, seeing as closures are not used.

r? @cmr