Summary:
In this interview, Clojure creator Rich Hickey suggests that the problems people associate with shared state are problems of time. He compares the lack of automatic time management in today's mainstream languages to the lack of automatic memory management in languages such as C++.
The ability to add new comments in this discussion is temporarily disabled.
Most recent reply: October 2, 2009 2:25 PM by
|
> Academics and language guys are usually shooting for some > platonic ideal and, in the mean time, practical, unsexy > tools (web servers splitting requests across cores, > java.util.concurrent.* when you have a specific use case) > will continue to be where progress is made. Just look at > the historical evidence.
Don't look now, but all of clojure's concurrency primitives are built on top of java.util.concurrent -- which is an excellent library, but doesn't really provide an API that "lazy, stupid developers" can use.
Besides that, if those folks can use JDBC (or whatever) with its transaction isolation levels and such, then surely they can use CAS (atoms) and STM, which are dead-brains simple in comparison.
- Chas
|
|
|
> > What do you think of the idea that you should > > write as much of your program as possible with > immutable > > objects (or data) and pure functions? > > I think the Pure Functional Programming people are leaving > in a fantasy land. They speak about Pure FP as if it makes > it impossible to introduce bugs, which, of course, it's > not the case at all. They blame the concept of "state" for > everything, which is absurd, to say the least. > I think there's a difference between the idea of "writing much of your program with immutable objects (or data) and pure functions" and "pure functional programming." What I was asking about in my question was the former, which is what I think of as a functional style, or attitude. It includes preferring pure functions over functions with side effects. Also, Rich Hickey didn't come across as a functional programming purist in his keynote or interview. One bit I left out of the article was about pure functional programming. Hickey said that the problem with pure functional programming is that most programs that we write aren't pure functions. Some are, but most aren't. One example that he gave of a kind of program that's close to a pure function is a compiler. It takes input, processes it, and writes output. But if you have a web app that allows users to log in, then you've got state to deal with, and that's not such a natural fit for pure functional approach. So what Hickey was suggesting I think is more of a a hybrid notion in which you try and use a functional style for most of your program, but for the stateful parts make it stateful. I try to do that in Scala as well, and it's the main way my programming style changed as a result of learning Scala, which I described here: http://www.artima.com/scalazine/articles/programming_style.htmlThe main difference I see between what I do nowadays in Scala and what Rich Hickey was claiming is important in his keynote is the degree to which the language enforces this approach. Scala makes it easier and more natural to use immutable objects than Java does, but it doesn't make it any harder to make mutable ones. Clojure by contrast, if you just stick with Clojure and don't drop down into Java, pretty much makes it impossible to mutate state (if my understanding is correct) except via the "time constructs," as he calls them, which Clojure provides. Because Clojure is running on the JVM, you could always do mutable stuff as much as you want from Clojure by calling into Java. I see Clojure's not letting you mutate state in unmanaged ways (except by dropping down into Java) as analogous to Java's not giving you any way to free memory (except by dropping down into C via JNI). Scala's approach to mutable state, by contrast, is more analogous to C++ approach to memory in that you can call delete, but if you want you can use a garbage collection library. Scala's approach to this is to let people us libraries and compiler plugins.
|
|
|
> if you make every public method in class date synchronized > + if you allow access to attribute only via methods, than > I strongly assume that there cannot occur any concurrency > problems.
Nope.
Concurrency challenges represent a larger set than the types of data corruption and visibility issues that synchronization addresses.
Peace,
Cameron Purdy | Oracle Coherence
|
|
|
> For example (to take an example from the article), A Date > object is inconsistent because of violation of the > mathematical function that defines the date. When we have > month = February and then we set the day = 31, we have > violated the definition of Date. There is no date > February, 31. It's not a problem of time, it's a problem > of partiality: at that specific point of computation, we > have violated the definition of Date.
It seems inconceivable to me that we could build any substantial arguments or analogies on top of an analysis of java.util.Date, other than to simply conclude that it is poorly designed for its stated purpose.
To wit, back in the days of the USSR, a worker goes to see the doctor for a pain. The doctor asks him, "What is the problem?" The worker shows him a spot on his arm and says, "Doctor, it hurts when I press here." The doctor says, "Well, don't press there." (Note: It's a Russian joke, so that's the whole thing; there is no punch line, but now you are supposed to laugh.)
So what about java.util.Date? Doctor, it hurts ..
Peace,
Cameron Purdy | Oracle Coherence
|
|
|
I see Clojure as a bit of a bait-and-switch; you come in thinking "I can interoperate with Java to my heart's content!" but very quickly you don't want to and when you have to do some interop, you stuff it into a side namespace to encapsulate the inherent Java ugliness. For example, I had a namespace with functions for XML parsing, and I seperated those out so that the result of XML parsing was a stream of tokens that were fully Clojure, and all the awkward Java class proxying and callback state stuff was inside the namespace.
That being said, people write Swing GUIs in Clojure (and quickly build up a library of functions and macros to make that clean).
Clojure lets you create and invoke methods on Java objects as much as necessary, which is of course impure and unsafe. You can call any Java method, and even use the set! special form to update public static or instance fields. But I have yet to need to do that in my own code.
I actually like the lack of options in Clojure vs. Scala's more flexible (and therefore, more complex) approach. I'm a better coder for adopting functional practices over the last few years, but that has accelerated as I've been using Clojure for the last 6 months or so.
Occasionally you see something awkward in Clojure that's simple in Java ... until you realize that there is a idiomatic way to accomplish what you want in Clojure that simply is different from Java ... which makes sense for a different language.
Total and predictable immutability combined with lazy evaluation is a powerful combination.
|
|
|
> It seems inconceivable to me that we could build any > substantial arguments or analogies on top of an analysis > of java.util.Date, other than to simply conclude that it > is poorly designed for its stated purpose.
The specific implementation or API is irrelevant -- the point is that any object that does not have value semantics (that is, every "pojo" or similar object that is just a bag of mutable values with undefined semantics) suffers from the same issues as j.u.Date. Make your own class with three int members, and the same points apply.
- Chas
|
|
|
Pardon my ignorance, but wouldn't this work? synchronized(date) { date.setMonth(february); date.setDay(10); }
|
|
|
> Don't look now, but all of clojure's concurrency > primitives are built on top of java.util.concurrent -- > which is an excellent library, but doesn't really provide > an API that "lazy, stupid developers" can use. > > Besides that, if those folks can use JDBC (or whatever) > with its transaction isolation levels and such, then > surely they can use CAS (atoms) and STM, which are > dead-brains simple in comparison. > > - Chas
I don't disagree. But JDBC doesn't ask you to change your day-to-day coding style *except* when you are working with the database. That's the great thing about it: whatever side computations you do are in the plain-old imperative style you know and love, and then you end up wrapping it all up and tying a transactional bow on top when you are done.
The current state of affairs is this: most of the time I can be stupid. When something concurrent comes up, I have to be smart for a bit to get something out of java.util.concurrent.* set up, and then I can forget about it.
The proposed solution is that I change the way I code everywhere to make the concurrent case easier to deal with for the library, language and hardware developers.
But I'm already doing pretty well over here in Imperative-stan, with the web server and database doing a lot of the concurrency heavy lifting for me. I don't want or need to change anything to get my web app running pretty well. Or at least well enough. So why should I adopt an awkward programming methodology across the board when I'm doing pretty well, day to day, without it?
Compare with the proposed analogy, the advent of GC: pre-GC I had to be pretty smart all the time. Then GC came along and, to a reasonable approximation, I could be pretty stupid all the time. (Or apply my admittedly limited brain power to problems other than memory management.) I wrote the same code I always did, but just left off the memory management bits.
That's dramatically different than proposing I change my entire programming style. GC was an unqualified, sand-pounding, sing-it-from-the-hilltops win with a negative cost: they *paid* me not to write code! Glorious.
Until I see a concurrency proposal that is that big of a win with very little cost, I'll remain skeptical of wide adoption. STM might be it, but it's going to have to be dead simple (from the users perspective) STM that melts into the background for most developers. And I remain skeptical that this is, finally, the moment that functional programming has been waiting for.
As always, worse is better, Carson
|
|
|
> Pardon my ignorance, but wouldn't this work? > synchronized(date) { > date.setMonth(february); > date.setDay(10); > } Yes, but in a very circumscribed context. That doesn't work for that other chunk of code that already has a reference to the date object and has now just had its "value" swapped out from underneath it. It doesn't work if there's any other code that mutates the object without locking on it. Manual locking, even in the simplest cases, is asking for trouble. - Chas
|
|
|
> That's dramatically different than proposing I change my > entire programming style. GC was an unqualified, > sand-pounding, sing-it-from-the-hilltops win with a > negative cost: they *paid* me not to write code! > Glorious. > > Until I see a concurrency proposal that is that big of a > win with very little cost, I'll remain skeptical of wide > adoption. STM might be it, but it's going to have to be > dead simple (from the users perspective) STM that melts > into the background for most developers. And I remain > skeptical that this is, finally, the moment that > functional programming has been waiting for. > > As always, worse is better, > Carson
Yeah, I can see that perspective. I'd respond with:
- Maybe there's a genius moment to be had that will make this entire discussion moot, but it isn't here, and I'm not aware of even a wisp of promise on the horizon that it's coming. There's work to be done now, and I'm not hacking and slashing through Java for another 10 years given a (very pleasant, IMO) alternative.
- This isn't about concurrency exclusively -- note my comment above about manual locking being a broken strategy even in a single-threaded program (due to simple shared references to mutable objects). We don't do much w.r.t. concurrency at all, but having an efficient, pleasant FP environment on the JVM that comes along with great libraries and a great community (talking about clojure here, just in case that wasn't clear) has made me a convert w.r.t. FP, persistent data structures, etc.
- I'm in the happy circumstance of not caring a whit about what's widely adopted or not. I just want a development environment that allows us to do our work in the most efficient, effective way possible; some critical mass within a community is necessary, but wide adoption isn't for my purposes. It's probably wishing for more engagement than I suspect is realistic, but I wish more people would take the same tack -- if they did, I'll bet we'd converge on a set of ideal solutions way faster than otherwise.
- Chas
|
|
|
Hi Carson,
> The current state of affairs is this: most of the time I > can be stupid. When something concurrent comes up, I have > to be smart for a bit to get something out of > java.util.concurrent.* set up, and then I can forget about > it. > > The proposed solution is that I change the way I code > everywhere to make the concurrent case easier to deal with > for the library, language and hardware developers. > I think the proposal (by which I mean Rich's idea of language support for auto-time management) is intended to make the case easier to deal with for you, not for them--primarily the concurrent case but also the sequential one. > But I'm already doing pretty well over here in > Imperative-stan, with the web server and database doing a > lot of the concurrency heavy lifting for me. I don't want > or need to change anything to get my web app running > pretty well. Or at least well enough. So why should I > adopt an awkward programming methodology across the board > when I'm doing pretty well, day to day, without it? > If you're not feeling concurrent pain and aren't overwhelmed by accidental complexity, then you indeed have no need to change anything. But it doesn't sound like you're really doing much concurrent programming, where you're trying to do concurrent things directly. If the library is doing it for you, then that's a good thing. A big design goal for J2EE was to take care of threads so business programmers could focus on writing *sequential* business logic.
But not all apps are like that. I am just polishing off the 1.0 release of ScalaTest, and it is a concurrent app. I use one Scala actor, a bunch of stuff from java.util.concurrent, one class that's synchronized, and one that uses wait/notify (and is synchronized too of course). The rest is sequential: a mix of imperative and functional style, but leaning primarily towards the functional style because I felt it made my code better.
I'm also not sure functional programming is as much awkward as it is unfamiliar to the mainstream. I.e., it is awkward to people because they are unfamiliar with it. I find it awkward sometimes, especially when I would need to use recursion to be full-on functional. I do use recursion sometimes, but since I'm working in Scala, I can also just use a while loop and a var, and I do that from time to time. I make sure it is all local, so each thread would get its own copy, then it's perfectly fine to do it the way its done in Imperative-stan.
If you forget the word "functional" and just say final variables, immutable objects, and methods that just return a value based on just the passed data without having any side effects, that really doesn't sound that unfamiliar or awkward. String is an immutable object. Is java.lang.String awkward for Java programmers to use? How about String.valueOf(5)? That's a pure function. I just operates on its input, returns a value, has no side effects. I doubt that's awkward for Java programmers, and neither are final variables. What is awkward, probably however, is using recursion instead of looping. Recursion isn't that hard to understand and I figure most Java programmers know what it means and how to do it, but it's not the way we usually have done things in Imperative-stan, so we're not used to it.
> Compare with the proposed analogy, the advent of GC: > pre-GC I had to be pretty smart all the time. Then GC > came along and, to a reasonable approximation, I could be > pretty stupid all the time. (Or apply my admittedly > limited brain power to problems other than memory > management.) I wrote the same code I always did, but just > left off the memory management bits. > > That's dramatically different than proposing I change my > entire programming style. GC was an unqualified, > sand-pounding, sing-it-from-the-hilltops win with a > negative cost: they *paid* me not to write code! > Glorious. > > Until I see a concurrency proposal that is that big of a > win with very little cost, I'll remain skeptical of wide > adoption. STM might be it, but it's going to have to be > dead simple (from the users perspective) STM that melts > into the background for most developers. And I remain > skeptical that this is, finally, the moment that > functional programming has been waiting for. > My sense is that there isn't one answer to concurrency, that we'll need a good toolbox from which to choose. Java provides locks and monitors in the language, and java.util.concurrent offers quite a few options in the standard library. Scala adds in actors and will add more things in the future. Clojure also provides several different options, CAS, STM, Agents. Plain old immutable objects and pure functions are helpful too. But I don't see any silver bullets. > As always, worse is better, >
|
|
|
> > > > > > - Some flavor(s) of Haskell emit C, and are as fast > as > > C > > > for many tasks. > > > > Undoubtedly. But my point is that C (and its > derivatives) > > can not be abandoned, because there are things that > pure > > FP languages can not do (like in-place quicksort). > > We're definitely talking past each other because of our > (likely very different) use cases and specialties. > > No one said that they should be abandoned, but the same > tactics that are relied upon in systems programming simply > do not scale to solving different classes of problems. > The fact that many environments are happy enough to get > t the performance benefits of lower-level languages when > necessary (e.g. [almost] no one writes sort routines in > clojure, you just "shell out" to Java, and wrap the result > in an immutable wrapper in O(1)) while working with > higher-level primitives the rest of the time.
You are talking as if systems programming languages can not be improved at all. I disagree. You can have a systems programming language that is as high level as possible. You can have your cake and eat it as well.
> > I promise you that the bottlenecks of most programs have > nothing to do with the speed of sorting routines.
You forget the accumulating costs of using high-level features.
> > > > Users of other languages are happy to > > > trade some performance for better semantics and > > > development processes. Different strokes, and all. > > > > And that leads to unresponsive, slow and bloated > > applications. > > Per the above, in many fields, that leads to applications > that *work*. If I had to contemplate building my current > projects in C++, Java, et al., I simply would not have > bothered -- the amount of manual bookkeeping would have > drowned us for years.
Please give us an example of such a project.
> > > > - "A graph of mutable objects is hard to understand" > > > because of the undefined or poorly-defined semantics > of > > > those objects' state. Programs consisting of > functions > > > and values are likely just as complicated (in some > > formal > > > sense of numbers of relationships between entities, > > etc), > > > but more understandable because of the well-defined > > > semantics. > > > > The same well defined semantics can be used on objects. > > But where? If there's an approach that provides similar > sorts of guarantees around mutable objects, I'd be happy > to educate myself.
The same sort of guarantees that exist for FP also exist for objects - it's the type system, not FP. Apply the type system to objects and, hey presto, you can have similar guarantees.
In other words, the extremely static and strong type system of modern FP languages can also be applied to other languages as well. The separation between FP/OO is artificial and invalid, because the two concepts (functions as first class entities and objects) are complementary, not overlapping.
|
|
|
> > It seems inconceivable to me that we could build any > > substantial arguments or analogies on top of an > analysis > > of java.util.Date, other than to simply conclude that > it > > is poorly designed for its stated purpose. > > The specific implementation or API is irrelevant -- the > point is that any object that does not have value > semantics (that is, every "pojo" or similar object that is > just a bag of mutable values with undefined semantics) > suffers from the same issues as j.u.Date. Make your own > class with three int members, and the same points apply. > > - Chas But the problem is not mutability. For example, make one class with 3 int bitfields that span exactly 32 bits; now there is no problem mutating the class, because the bitfields will be written/read in one go. Here is your problematic class: public class Foo {
private int member1;
private int member2;
private int member3;
void set(int m1, int m2, int m3) {
member1 = m1;
member2 = m2;
member3 = m3;
}
}
The above class has a consistency problem when called from different threads. But what about this class? public class Bar {
private int member;
void set(int m1, int m2, int m3) {
member = m1 | m2 | m3;
}
}
The above does not have a problem, because a 32-bit word will be set with one instruction, and so invariant violation can not be achieved. So the problem clearly is not mutability.
|
|
|
> I'm also not sure functional programming is as much > awkward as it is unfamiliar to the mainstream. I.e., it is > awkward to people because they are unfamiliar with it. I > find it awkward sometimes, especially when I would need to > use recursion to be full-on functional. I do use recursion > sometimes, but since I'm working in Scala, I can also just > use a while loop and a var, and I do that from time to > time. I make sure it is all local, so each thread would > get its own copy, then it's perfectly fine to do it the > way its done in Imperative-stan. > > If you forget the word "functional" and just say final > variables, immutable objects, and methods that just return > a value based on just the passed data without having any > side effects, that really doesn't sound that unfamiliar or > awkward. String is an immutable object. Is > java.lang.String awkward for Java programmers to use? How > about String.valueOf(5)? That's a pure function. I just > operates on its input, returns a value, has no side > effects. I doubt that's awkward for Java programmers, and > neither are final variables. What is awkward, probably > however, is using recursion instead of looping. Recursion > isn't that hard to understand and I figure most Java > programmers know what it means and how to do it, but it's > not the way we usually have done things in > Imperative-stan, so we're not used to it.
That's good for simple things like a string, but if more ambitious things are tried, then FP gets very complex quickly. Just witness how difficult it is to manipulate trees (search for Huet's zipper pattern), for example...I seriously doubt that the majority of programmers will ever understand such things as the zipper. And then there are other complex stuff...for example, in trying to program a game in Haskell, one has to use something like Haskell/Arrows and be familiar with concatenative programming...pure FP certainly isn't for the common folks.
|
|
|
> That doesn't > work for that other chunk of code that already has a > reference to the date object and has now just had its > "value" swapped out from underneath it.
Yes, but this is what software design and planning are about. Clearly the "other chunk of code" was not designed to support this scenario.
|
|