Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The problem with the mutability debate is that most examples show two programs, one mutable and one immutable, on the scale of about 20 lines of code. At the small scale, it's a toss-up which is better. Often the mutable solution's more intuitive for most programmers, and sometimes just flat-out superior: more clear, easier to understand. Mutable state isn't evil. It's necessary in the real world. It's just that stateful actions don't compose well, especially when you involve concurrency or Big Code problems. Good programmers learn that they need to manage (not eliminate) mutable state. That's what FP is about.

So the aesthetic dominance that FP advocates hope to establish with their 20-line A/B depictions doesn't come through, because the truth is that the problems with mutable state very rarely show up (except in contrived, over-complex examples) at 20 LoC. At 20 LoC, the snippet you'll like better is going to be the one you're most familiar with. The real differences show up at 2000 LoC, which can't be put in a PowerPoint.

Immutable programming is somewhat less prone (but not immune) to complexity creep. For example, you see 500-line for-loops in corporate software all the time. The conceptual integrity is gone because so many people (who never learned what the others were doing) have added tweaks to it.

The difference between mutable and immutable programming is that making that sort of change to an immutable program also changes the API, unless it's purely a performance tweak (e.g. plus(2, 2) still returns 4, but does it faster). If you add logging to plus in a purely functional world, you change its signature from (Int, Int) => Int to something like (Int, Int) => (Int, String). As you might guess, that's a double-edged sword. Sometimes you want people to be able to add "purely stateful" (i.e. no API changes) effects without changing a signature... but very rarely.

So I think the major upshot of immutable programming is that it makes it impossible to add many varieties of complexity that corporate engineers tend to add silently (in pursuit of a short-term hack) without changing an API and breaking the build. This slows down complexity creep, and that's a good thing.

It's the reduction of that externalized-cost/complexity-creep dynamic that makes FP superior, in my opinion. A 60-line referentially transparent function really isn't less evil than a 60-line method of an object. They're both fucking incomprehensible, in most cases. You're just less likely to see the 60-liner in a mature FP codebase. Also, because functions compose better than stateful actions, it's usually a lot easier to break large functions up long before they get anywhere near 60 lines. (In my opinion, double-digits are "warning" territory and 25+ lines means it should almost always be split, at least into inner functions.)



When you have state...I mean real state, it sure is nice to encapsulate that state in an object rather than in what is basically an unencapsulated monad. OO supports state encapsulation, pure FP basically does not, that is a big deal. Immutable programming sort of side steps the issue, that state is needed at all, that an interactive program can somehow be stateless is ridiculous, even many batch programs require some form of state (even if it is unencapsulated in a monad).

Your 60-line function decomposed into nice small parts using beautiful composable abstractions is an FP pipe dream. Yes, if the problem is well understood, someone has thought about it for a long time and has come up with some beautiful abstraction that works for a narrow set of related problems. Now, as soon as you venture outside of a well-understood/nice abstraction domain, your code is just as bad in FP, if you can figure out how to implement at all.


Could you explain why a monad is unencapsulated? As far as I can tell, using monads is a far superior way to encapsulate state, since it's impossible to use the state without also operating within the type universe that you've defined for your stateful calculations. Therefore, you can trivially tell if a piece of code is relevant to the state of your program, and it's enforced at compile-time.

Further, since the actual state values are immutable, you can store references to them indefinitely. In most OOP languages, the whole concept of a "state at this point of execution" is completely unsupported, and that state is inaccessible to the programmer.

Edit: I wrote this assuming we're talking about a sufficiently advanced static type system here, such as Haskell's. I've implemented monads in Clojure and while they can be useful, of course in a dynamic language they provide less safety since it's trivial to escape the monad.


The problem with monads is that they DO leak into the type system. What effect the object has must be exposed for type checking reasons, and it can't be encapsulated, hidden, changed transparently, and so on. Try iterative algorithms, UI programming with views and models, an interactive code editor, etc...you get trapped quickly by the type system. The point is, you often want to be oblivious about what that object is doing and when it is doing it.


I'm sorry, I don't see the problem. You can encapsulate away the state behind an opaque type so that it can be only accessed by functions that you have defined. Whether this is a good choice is up to the programmer, but it's common practice in haskell to use domain-specific types wherever it makes sense.

For UIs and such, in addition to monads there are more powerful abstractions, but at no point is it necessary to leak information to the client. The common UI toolkits tend to have some impedance mismatch with FP because they've been designed with OOP in mind, but this is not the fault of FP.

Of course, in FP it often doesn't even make sense to encapsulate everything. Why hide useful data when it's guaranteed that any user will not be able to misuse it?

Haskell provides tools for abstraction that are IMO vastly superior to anything I've seen in an OOP language. If anything, you're more likely to have leaky abstractions and failed encapsulation in Java or C++ compared to Haskell, simply because of mutable state, closed classes, and limited expressiveness of the type system.


Ah, they just didn't design their UI libraries right? I hate this argument, because its easily proven true (someone just has to design a "right" library) and impossible to prove false (the "right" library could exist, it just hasn't been built yet).

The problem with Haskell, which isn't a problem in an impure FP like Clojure, is that you can't define objects at all (in the sense that objects completely encapsulate state). Yes, you can do a lot of nice combinator tricks, but these only work well for well understood problems, and so FP practitioners spend most of their time trying to understand problems very well so that they are amenable to their elegant abstractions. Whereas if I just use an OOP language (or a pragmatic FP language), I just solve the problem in a hacky way without needing to completely understand its essence (which, for me at least, only comes after I've solved the problem N times in N different ways!).

Closed classes suck, but not all OOP languages restrict you to closed classes. Scala does very well here, and traits are wonderfully expressive.


Oh really? A pipedream you say? Oh ok then...


Let me reply your one liner with my own: nice elegant composable abstractions are great when they work, and very horrible when they don't.


I don't see how this is any different for "OOP" though (or anything in life for that matter)?

Things are always great till they don't work, and usually are absolutely horrible when they don't.

I see your pipe dream on a daily basis as do many other competent functional programmers so I have to disagree with your entire argument which seems to more related to human error than programming paradigms getting in the way.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: