3 ms·
My biggest concern with something like this is how it affects debuggability and the principal of least surprise, for a couple of reasons. The biggest is that in
by openasocket 9y ago
My biggest concern with something like this is how it affects debuggability and the principal of least surprise, for a couple of reasons. The biggest is that in the presence of bugs. If there is a bug in foo but you only use foo in the context of foo x (bar x) then all those operations get transformed and you end up with the correct behavior even though your code has a bug that will suddenly appear when you use foo in a way that that rule isn't applied. Or there's a bug in superOptimizedFooBar, so you correctly write "foo x (bar x)" which is correct, but you get the wrong results, and you may waste time trying to debug foo and bar not realizing the rule replacement. And there is also the possibility of bugs in the rules themselves. In general, I'm a little hesitant to use something that is replacing my code behind my back.
It is very interesting, though, and is a good avenue to explore. I've got some reading to do :)
- mrkgnao 9y agoYou're right, of course. There are ways around this: for one, without optimization (i.e. -O0) you don't have any rewrite rules firing, so any discrepancies in behavior can be tracked down this way. In practice, most of the libraries that include tons of rewrite/specialise/inline (ab)use are either "core" libraries (like vector/bytestring) or have a large userbase (e.g. Conduit), and rules don't really change too much from version to version, so this has never actually had the detrimental effects that "silent replacement of code" might have in the worst case. This might[0] sound similar to apologetics for dynamically-typed languages: the only real answer to your question is that rewrite rules are ultimately a way to improve performance, and they come with caveats usually associated with replacing clearer algorithms with faster ones. (I'm thinking of the adaptive sort from the C++ STL, which iirc uses different sorting algorithms for differently-sized containers to maximize expected performance. It's not exactly intuitive that vectors of size 100 and vectors of size ~10000 are sorted differently, is it?) Of course, the only verifiably-correct way to do these things is through the use of machine-checkable proofs of the transforms. The well-known "foldr/build" rule that many optimizations of linked-list functions reduce to, for instance, has been proven outside of GHC, and there are usually similar proofs for other rules known to the library author. The appeal of dependently-typed languages is how they are a nice vehicle for the inclusion of those proofs in library code: if the compiler can't verify that your rule makes sense, it can complain instead. You then end up supplying a proof, or saying "just believe me, okay?" ;) [0]: "In practice"? Seriously? Next thing I know I'll be parroting "in the real world" and so on...
- mrkgnao 9y agoThis post has a good overview of what can be gained by the use of these techniques: http://www.randomhacks.net/2007/02/10/map-fusion-and-haskell-performance/ http://www.randomhacks.net/2007/02/10/map-fusion-and-haskell... > Using just these two rules, I saw a 225% increase in the number of nodes processed per second. Under the right circumstances, GHC can even outperform C code by applying these kinds of inter-procedural optimizations. Also, we have QuickCheck, which allows us to check properties by randomly generating testcases. The post I linked mentions this.