Programming and thinking the functional way
peteratt.com
peteratt.com
It made me think that recursion, no matter how short the code looked, was terrible.
Now I realize that recursion, thought about properly, requires so much less mental overhead. All those lines of code that vanished were really whole concepts and ideas which could vanish as well.
Of late, my style has been refined by working with theorem provers which make the ultimate connection. Recursion is absolutely nothing more than mathematical induction. Each recursive call is just you invoking the inductive hypothesis.
What that connection really drives home is that when writing a recursive definition you need to do so very few things. First, you need to make sure your definition has a good foundation—you must list all of the conditions where it terminates and ensure that they truly found and support the algorithm/proof. Then you must write the inductive engine that will burn away whatever inputs you receive down to the foundation you just made.
The release is in noticing that those inductive engines can be primed on the very tiniest actions—and those tiny actions are all you, the implementer, are responsible for.
When writing your inductive step consider only the things that are novel about this case. If I'm working with an input list and inducting on `cons` then all I must do is consider what happens to the head and tail of the list. Everything else will be handled by the turning of the inductive engine.
Now a good algorithm just feels like someone walked me into a room with a gigantic domino structure. I walk around it slowly and determine the ends and then just flick the single domino which will churn away inevitably the rest of the algorithm.
And, by extension, structural induction === sum & product types + pattern matching destructuring for recursive functions. The fact that the type checker can make sure you've got your base cases covered is just gravy.
"The goal of recursion and loops is exactly the same, you want to define something in terms of itself.
That's what the loop does; it repeats a computation and something gets smaller...
the loop variable or when you for each over a loop you're picking out the next element..
That's exactly what a recursive definition tries to do, you're trying to define a function, in terms of a smaller version of it's argument."
http://channel9.msdn.com/Series/C9-Lectures-Erik-Meijer-Func...
In Turing complete languages they're both special cases of fixed-point equations, though, so in that sense they both have the same goal.
It is at the core of many mobile to internet gateways out there. At the core of WhatsApp. Some databases (Riak, CouchDB) and message queues (RabbitMQ).
Language-wise, besides concurrency constructs, you get pattern matching, immutable data structures (and bindings). Unlike Haskell, all types are dynamic (but strong).
Also a counterpart to Learn You A Haskell For Great Good is Learn You Some Erlang For Great Good:
http://learnyousomeerlang.com/syntax-in-functions#pattern-ma...
For example the quicksort sample would look like:
sort([Pivot|T]) ->
sort([ X || X <- T, X < Pivot]) ++
[Pivot] ++
sort([ X || X <- T, X >= Pivot]);
sort([]) -> [].
But it is the large applications that ends up breaking my brain. I am convinced once you learn programming in an imperative or object oriented language you brain just become molded to that model of thinking and it is very hard to adjust (it is nevertheless very useful to try).Really? Large Erlang applications can be understood as a collection of objects that happen to be running concurrently, and I explicitly mean that OO intuitions about responsibility, identity, and substitutability can be fairly directly used. In that sense, I don't think Erlang is actually a very functional language in practice. Erlang programs tend to look like one's initial, high-level overview of a how an OO program will look when you first draw a sketch, with each circle in the diagram becoming a process (or more accurately, type of process, e.g., a "connection" process that may be instantiated once per connection).
Now that I've spent some time playing around with it, I think I like Erlang better than I like Haskell; I used to think I disliked dynamic typing but it turns out I just don't like the way Ruby handles it. Erlang feels really good to write code in, and it feels a bit less finicky than Haskell is.
If anybody wants to try functional programming and feels like they just can't get along with Haskell, I'd definitely recommend you give Erlang a try. It's not great at everything but I'd highly recommend it as something to try.
[0] I mean in the "no/minimal mutable state" sense.
EDIT: Removed potentially confusing aside.
EDIT#2: I guess I should elaborate: The idea is that you just have the MutableRef actor accept two messages: Set(X), Get(X). The basic idea was to just have the actor continually send itself Set(X) messages with the current value -- thus exploiting the messaging to keep mutable state.
It's really the "interpreter" of the IO monadic values which causes all the side effects. I am aware that there are some issues with this interpretation, but if you want to get pragmatic it's really a question of X% pure/(100-X)% impure while keeping X as close to 100% as possible. Haskell can do that for X arbitrarily close to 100% (depending on how much abstraction/effort you're willing to go through).
1) If it's simple to imagine the computer doing something, it should be easy to program.
2) If it's simple to analyze something mathematically, it should be easy to program.
The first path leads to C-style programming with pointers and loops. The second path leads to lazy pure functional programming and beyond.
Personally, I'm in the first camp. Small functional programs are often shorter and more beautiful than their imperative and OO counterparts, but large functional programs quickly accumulate so many abstractions that they become incomprehensible to me, more so than large imperative or OO programs.
Maybe that just says something about my intelligence, or maybe the functional camp hasn't yet figured out how to write large programs in a readable way. Case in point, see the list of operators defined by the popular Lens library in Haskell: http://hackage.haskell.org/package/lens-3.8.5/docs/Control-L...
Most of that has to do with optimizing for reuse and proper type inference. It's a cheat to expose the Haskell subtyping relation for more leverage.
A better way to understand lenses should be to consider a package like lens-family-core which keeps to its roots.
I wouldn't mind if haskell library designers took that practice to heart.
Cf http://ro-che.info/articles/2014-04-24-lens-unidiomatic.html
addart a = array ((-1,0),(n,m+n)) $ z ++ xsi ++ b ++ art ++ x
where z = ((-1,0), a!(0,0)) : [ ((-1,j),0) | j <- [1..n] ] ++ [ ((-1,j+n),a!(0,j)) | j <- [1..m] ]
xsi = ((0,0), -colsum a 0) : [ ((0,j),0) | j <- [1..n] ] ++ [ ((0,j+n), -colsum a j) | j <- [1..m] ]
b = [ ((i,0), a!(i,0)) | i <- [1..n] ]
art = [ ((i,j), if i == j then 1 else 0) | i <- [1..n], j <- [1..n] ]
x = [ ((i,j+n), a!(i,j)) | i <- [1..n], j <- [1..m] ]
((_,_),(n,m)) = bounds a
One letter variable names, argh!You make it sound like we've figured out how to write imperative large programs in a readable way.
Haskell programmers tend not to write large programs, period. Instead, we build lots of libraries until our problem is trivial to solve.
Case in point, see the list of operators defined by the popular Lens library
Not really a fair example. Lens is not a program (large or otherwise), it is a new programming language embedded within Haskell. Viewed within that context, its bevy of operators is somewhat justified. The other thing to note about it is that lens was designed specifically to avoid clashing with variable and operator names defined in other popular libraries so that users could import it unqualified.
But if the libraries that you write, taken together, are large, then you are in fact writing a large program. Calling part of it "libraries" doesn't change that. (So far as I can see, that's no different than a C programmer writing lower-level functions, or an assembly language programmer writing subroutines. Everybody does that - at least, everybody sane.)
http://augustss.blogspot.ca/2011/05/more-points-for-lazy-eva...
So, how is that different from any other language since the invention of functional/procedural decomposition? Programmers generally write libraries and then compose them, rather than writing large monolithic programs, irrespective of language.
http://hackage.haskell.org/package/lens-family-core-1.0.0/do...
public class QS {
public List<T> Quicksort<T>(List<T> l, Comparator<T> comp) {
if (l.size() <= 1) {
return l;
}
List<T> lesser = new ArrayList<T>();
List<T> greater = new ArrayList<T>();
T pivot = l.get(0);
for (T t: l) {
if (comp.compare(pivot, t) <= 0) {
lesser.add(t);
} else {
greater.add(t);
}
}
List<T> out = new ArrayList<T>();
out.addAll(Quicksort(lesser));
out.addAll(Quicksort(greater));
return out;
}
}I don't think this is a fair comment considering the in-place Haskell implementation isn't incredibly readable either.
main = let nan = 0.0 / 0.0 in
do putStrLn $ show $ quicksort [nan, 1.0, 2.0, 3.0]
putStrLn $ show $ quicksort [1.0, nan]
[NaN]
[1.0]
Sort routines should not return a list of a different length than their input.Haskell is elegant & Java isn't, but cherry picking examples always comes with a risk of making your argumentation straw-man-ish.
Would be interesting to see some examples where imperative isn't so horrible, and how Haskell compares. The in-place sort the author mentions, for example.
Even that is only true if you consider the concise and readable nature of the Haskell code to be the most important factor in the elegance of the algorithm.
To me, the elegance of true quicksort is that its in-place nature is memory efficient, and consequently also cache-friendly on modern hardware, resulting in excellent real world performance.
If you consider the underlying nature of the algorithm to be more important to its elegance than superficial presentation details, then the typical 3-line functional implementation is clumsy by comparison, and equating the two is at best an appeal to having a sufficiently smart compiler.
In reality, of course, both the aesthetics and the underlying behaviour matter, so I'm not sure it's particularly helpful to promote any language as being superior on either basis without also considering or at least acknowledging the other.
The Java version doesn't really have a lot of unnecessary fluff. What, it's not a static method and has instance variables? So what? That doesn't add a lot of verbosity and is NOT the crux of the author's argument either.
The instance variable in class is an important strawman the author added to Java. He's trying to show the need of "state" in Java, which is not needed in a sensible Java version of qsort, as all data can be passed in parameters.
He also made the statement that the instance variable is needed for recursion in Java (!) to "substantiate" (make up) the excuse for using instance variable in Java.
And yes, those are called strawman.
Actually this programmer is me, right now at the moment of the writing.
The good:
1) The definition of the algorithm is clear. It shows "how quicksort works."
2) It's trivial to see (and prove) that the function will terminate, and almost as trivial to prove that it will result in a sorted list. So, it is easy to show correctness.
3) The polymorphism makes this an easily reusable function right out of the box.
The bad:
1) That implementation is very inefficient. At a glance I think it would be O(n^2) time. (Edit: this is misleading, because it's only O(n^2) in the same way that quicksort is always O(n^2). It is inefficient in terms of memory usage, though. And possibly other ways; for instance, I'm not sure how laziness would affect this. But I don't want to be spreading FUD...)
2) The "efficient" implementation given at the bottom is just as inscrutable as any other quicksort implementation I've seen. More so because of the monadic code, single-letter variables (pr?) and opaque library function calls (unsafePartition?) being made. And I'm not even sure that it would work on a list, although since V.Vector appears to be a type class, perhaps list is an instance of it.
3) Both the efficient and inefficient implementations are concise in large part because of their use of library functions. This is often a good thing: Haskell provides a great way to abstract things because of its parametric and ad-hoc polymorphism, and allows for a lot of reusable code. But it comes at a cost too, which is that the actual instructions you're giving to the machine are very far removed from what the computer is doing. Who knows how much code is actually executed, how deep the rabbit hole goes, to translate those beautiful 4 lines into actual machine instructions? With Java, it's precisely visible what the machine is doing to execute your code. This is much less the case with Haskell.
I suppose you could say (broadly) that functional languages excel at expressing what your program should do, while imperative languages excel at expressing how your program should do it. There are cases when you care more about the former, and cases when you care more about the latter. The reason I take issue with the quicksort example, is that list-sorting is a case where you definitely care more about the how than the what.
-------------------
EDIT, since two people called me out on it: It was an overstatement on my part to say that it's "precisely visible" what your Java code will translate to, but to suggest the two languages have the same degree of abstraction from the CPU is ridiculous. The code translation from Java to bytecode is quite straightforward, because most of the optimization occurs at runtime with JIT. There is a reasonably direct relationship between the code you write, the bytecode it gets translated into, and the instructions executed at runtime. At the end of the day, Java code consists of a series of instructions for the computer to follow, while on the other hand in Haskell, you aren't even technically giving instructions at all -- you're just writing equations. Compiled Haskell code is completely inscrutable.
Also, I should note that I am an enthusiastic Haskell hobbyist, and write it on almost a daily basis. Although I might not come across it here, I am a huge fan of the language; outside of the languages I use at work, by far the one I use most is haskell.
listQsort :: Ord a => [a] -> [a] listQsort = V.toList . qsort . V.fromList
Also: > With Java, it's precisely visible what the machine is doing to execute your code. This is much less the case with Haskell.
This is very much a false statement. Java code runs through the JVM and its memory bloat, whereas haskell is compiled to native code along with its runtime. Both involve significant abstractions away from the actual machine instructions. It is more accurate to say that Java makes you think you can see what the machine is doing, but it is also far removed from machine code. One advantage is that with haskell, the compiler can reason about your side-effect-free code and optimize much more than java code.
Basically, bang out a slow but obviously correct implementation, and then iterate to convert it to be efficient, perhaps using things like 'equational reasoning'. However I haven't much experience with this technique yet...
I believe this is the technique used in Pearls of Functional Algorithm Design by Richard Bird (I have yet to read it)
You don't, because it's near impossible. In Haskell you have defined quicksort, not instructed your computer how to do it. That's the reason it's beautiful, but also the reason it's hard to iteratively refine your soluion. There is basically only one definition of quicksort: your program, and you can't iteratively "refine the definition"
You can try to make it in-place in Haskell but there is no clear transition from your initial version to the in-place version.
The in-place haskell one looks something like this (and that java code shows no sign of envy now). Answer: like this http://stackoverflow.com/questions/5268156/how-do-you-do-an-...
This is my main problem with functional programming: it makes it very easy to go 90% of the way in a very elegant matter. Once you hit that brick wall though, you come to a point where you'd cut an arm off for a mutable array.
Perhaps the solution isn't to write horrible Haskell but rather either use a less strict functional language OR outsource that 10% of the code to an imperative language, rather than making contrived, complicated code like the in-place Haskell quicksort?
This post explains what I'm talking about: http://www.atamo.com/blog/how-to-read-pearls-by-richard-bird...
But I'll probably pick it up at some point in the near future.
Personally I don't find that to be true, it's perfectly logical mostly. It's definitely worth picking up imho.
The same criticism can apply to Java. But it's not a particularly good criticism. Unless you're interested in quantum physics, you really don't want to know what the machine is "really doing". You want to have useful abstractions that you can rely on.
The rabbit hole goes very deep indeed, and a lot of concepts that programmers treat as concrete are themselves abstractions that hide a lot of complexity.
The difference in level of abstraction between Java and Haskell is much smaller than the difference from either of them to what the actual machine is doing. In both cases you have a compiler, a language runtime, an OS & kernel, and processor microcode in between you and "the metal". Actually Java has one extra step, because javac emits bytecode that needs to run in the JVM, whereas ghc emits binaries that can be run directly by the operating system.
(Your comment about O(n^2) in time is beside the point -- quicksort is always worst case O(n^2), so it wouldn't be quicksort otherwise. All the implementations we're talking about have that same asymptotic behavior. The problem with the naive Haskell implementation isn't the big-O time, it's the big-O memory, which went from O(1) to more like O(n log n).)
The expectation is that pure functional languages gain a lot of benefits from (for instance) referential transparency which allow for optimizations which can not be provided by the compiler in languages which don't provide the same benefits to developers.
It's also true that this is potentially just theoretical. Sure, there's no way to ensure these optimizations exist given your compiler's implementation, but the article actually does mention that this has other issues and it's only for demonstrative purposes.
So, chances are it's a non-issue and if it is an issue then you still have the power to solve the problem later. No need for premature optimization, right?
Would it really change the argument if the Java code used a static method and passed all variables as arguments, instead of using instance members?
Also for any C# peeps who already have had experiences using lambda expressions on the awesome .net platform. How was performance there?
Beware, beware.