Can functional programming be liberated from the von Neumann paradigm?
conal.net
conal.net
State and time are always going to rear their ugly heads for functional programmers (often in the form of "the i/o problem"). There's just no way around it. This isn't to discount functional programming as a tool in the toolbox, but "philosophically pure" functional programming is an ideal as platonic and unreachable as the math it is derived from.
It's sad some programmer fall into the purity trap and try to make things as functional as possible and end up taking a lot more time than they normally would have, make the code incredibly hard to understand, etc.
I once fell into this trap and paid dearly. I'm a university student and for one of my semester projects I decided to try out pure functional programming. I ended up failing that class because I didn't finish the project, and doing bad in others because I spent ridiculous amounts of time doing the impossible project. (The project itself was easy - a simple 2D sidescroller game in C++, something I could have done in a week if I had been sensible; but writing a game in functional style in C++ proved to be incredibly hard.)
It seems that you have more experience with the imperative style than the functional style. And yes, it's always good to try a new style you aren't yet comfortable with, but you shouldn't do that in an important project. Also, you might have had better luck with Haskell or Lisp rather than C++. But here again, you shouldn't do that in a time contrained project if you aren't fit in that language yet.
Instead, you could have taken the week to implement it in the style you're comfortable with, and rewriting or refactoring it into another style to play around with it. That way, you would not only have eliminated the risk of failing. You would also have learned more, being able to compare both styles side-by-side.
It's good to be too pure for learning purposes, because it makes you comfortable with a lot of nitpicky stuff. When you move towards commercial software, you won't get an environment that encourages you to take on the risky stuff. "Lean and mean" is the order of the day. So having the other experience gives you an edge in perspective.
As with any other style (OO, etc.), you won't get it right in your first attempt. It takes time to get a feeling for good design - in any style.
Not really a good idea when you are starting out, I guess.
(It's interesting all the way though, but it gets Really Interesting about 20 slides in.)
That's why monads are an extremely powerful concept, and not just a hack to "avoid time". Using monads, you can compose evaluations in a certain order (model time), but that doesn't automatically mean they will be actually evaluated in that order (computing time). Well, if you use IO monads this will actually be the case. But you are free to define other monads which allow you to do strange things like looking into the future or checking multiple alternatives at once.
Pure functional programming forces you to always make an explicit distiction between these two kinds of time, and that feels buerocratic to those who are used to confound them. However, making that distinction usually yields to much better interfaces. So there is an extra effort, but also a big gain.
I strongly recommend to read Philip Wadler's The essence of functional programming before talking undifferentiated about the broad concept of time: http://homepages.inf.ed.ac.uk/wadler/papers/essence/essence....
[Byte] -> [Byte]
main [data:rest] = ...
(sorry if I'm making up syntax or types, haven't properly learned Haskell yet). Well, my friends tell me, the problem is that often times you have to deal with timing, exceptions, etc. Well, fine:
[(Byte, Integer, Error)] -> [Byte]
main [(data, time, err):rest] = ...
As you said, no reason actual evaluation has to correspond to the logical order of evaluation.
Though that's a good point, when it comes time to be opening files on the disk, it inevitably becomes stateful again.
But non-functional code is much harder to debug (spooky action at a distance, etc) so a little bit more work upfront to isolate the non-functional code from the functional will make the debugging/maintenance phase (which are considerably longer than the 'initial writing' phase), easier.
It might make sense to say, "we can't build an effect-free computing machine", because we use transistors, and transistors get hot when they sink power, and things getting hot changes the entropy of the Universe... but it doesn't make sense to say, "we can't model an effect-free world, and program is if the world is effect-free".
(In fact, without effects, there is no world.)
Because apart from nothing, I'm not entirely sure what the author really thinks 100% pure functional code should do, when even input and output states are too impure to be allowed inside his Sanctified Garden of Jesus-Code, despite the fact that it's that mapping that we're explicitly interested in whenever we run a program. As far as I can tell, it's the "run a program" step that the author has a real problem with, and while he's welcome to subvert that paradigm, I don't see any hint of a way forward from it...if your program is meant to be fully composable at a level more granular than "program", shouldn't you be writing a library instead? Is that the suggestion?
From what I can tell, the author hasn't figured this out, either, he just thinks someone should.
readFile :: String -> IO String
readFile filename = ...
At time t1, if I invoke it it returns say:
IO "Contents at t1"
At time t2, if I invoke it it returns say:
IO "Contents at t2"
Given that we supplied the function with the same argument (i.e. fname), and it returns two different values, how can they be said to be equal? I'm probably totally misunderstanding this but anyway....
Of course, get is a pure function mapping (State a) -> (State a) a, and the value hidden in the state is different between successive function calls.
Now just treat IO as State World, and think of the compiler as the function execState initialWorldValue.