Rules for Writing Clojure Programs
twoguysarguing.wordpress.com
twoguysarguing.wordpress.com
(reduce #(assoc %1 %2 (inc (get %1 %2 0)))
{}
"abcaaabcccaabrrcb")
EDIT: While it's shorter and (in my opinion) a lot clearer than the OP's solution, it's also consistently faster. Functionnal code and composition is cool, but i think the author got trapped into making his code beautifull, or maybe just into the wrong algorithm for the job. It happened to me before in clojure and other functional languages (frequencies "abcaaabcccaabrrcb")
with some optional sorting if that's required.Heard comments such as:
“Huh.”
“I can't believe -> and ->> are being demonized now.”
”I think if you're calling things rules you should be somewhat careful about how you phrase them...”
“Someone is wrong on the internet.”Since you're apparently familiar with both Clojure and Erlang, what are your feelings regarding the concurrency model in each?
The clojure model means state is easy to move around. This is awesome. The Erlang model makes the server-like nature of each component very explicit, which can significantly reduce the complexity of some types of systems.
I always felt like Clojure was attempting a permissive system, whereas Erlang really is pretty up-front about the environment it's designed to work in.
I keep meaning to fiddle with Reia but never got around to it.
If bugs result in irregularity, then making regularity explicit (by using ->> or other macros/higher-order functions) can be a good thing.
That's a very good point, and a very hard bug to see when you "get lazy" and use a, b, c as step names.
Solution from the comments: (apply merge-with + (map (fn [c] {c 1}) “abcdaabccc”))
Unless of course they just didn't think of the fact strings are seqs in clojure by default.
edit: I could be crazy, but one problem with using apply is you force the realization of your object, so while in THIS case it's fine, using larger amount of data would cause a problem. IIRC more space sane way to do this is use (reduce {} (merge-with + %) list)
and man I MUST be rusty, I can't remember if the default value comes before or after the function, and no repl handy. Hopefully the point is gotten across :)
(defn count-words [coll] (reduce #(merge-with + %1 {%2 1}) {} coll))
(reduce #(update-in %1 [%2] (fnil inc 0)) {} "abcdaabccc")Another thing to consider is if you want to get fancy (persistent! (reduce #(update-in %1 [%2] (fnil inc 0)) (transient {}) "abcdaabccc"))
Still no REPL but this should work and take advantage of transients if you're doing it against a bigger dataset (which I assume you care about since you also worried about laziness)
(pseudo-code) :
<code> myset = {}
foreach c in mystring: if myset[c]: myset[c] += 1 else: myset[c] = 1
myset.sort(fn x,y: myset[x] < myset[y])
</code>
As I said, it's all about opinion (and I know there are million ways to simplify that or make it different, i.e. myset.get() or whatever), but I find the imperative approach way easier to gasp at first glance.
Compare that to:
(let [a (sort str) b (partition-by identity a) c (map (juxt first count) a)] (sort-by second > c))
First, I don't really like a,b,c as name variable (to be fair, myset and mystring are terrible too). Second, I find there are so many extra step to resolve this simple problem.. we need: partition-by, we need identity, we need juxt (wtf is juxt?). Third, wtf are first, count and second..?
I know high-level function are kewl, and working with lists are even cooler and using all of this in a 4 lines in a pretty clean way is again even cool-cooler. However, when I read this, even if there are less lines than the imperative way, it still takes more time to understand what the code is doing.
When I think about this problem, my mind immediately thinks "well, I want to take all the characters, group them into buckets, one per letter, and then count how many are in each bucket." I don't think "I want to make an empty map, starting with each character mapping to zero, iterate over every character and increment an associated counter in the map, and then read out the counters at the end." If I have to write the second -- because my language has bad list libraries or no higher-order-functions -- I have to "translate" the first into the second in my head.
The Clojure code expresses the computation in exactly the same way it lives in my head. The imperative code expresses that like a computer does it, and to even understand it I have to "step through" it in my head a little bit to make sure the computer is doing the right thing.
(defn frequencies
"Returns a map from distinct items in coll to the number of times they appear."
{:added "1.2"}
[coll]
(persistent!
(reduce (fn [counts x]
(assoc! counts x (inc (get counts x 0))))
(transient {}) coll)))
which I think is pretty much just Raphael's solution.(That's not to say that it's actually good code—the article itself points out the problems—but those problems can actually be fixed by modification, not replacement, of the code, which is what we should all be striving for.)
But it has provoked those sentiments in nearly everyone who's commented. Including the very comment you're replying to!
I don't see anything about the original example that makes it less prone to being rewritten. Nothing's going to stop a programmer who's hell-bent on rewriting something. And "modification" and "replacement" are not that different anyway, especially when the scale is small.
str = "abcaaabcccaabrrcb"
dict = {}
for ch in str:
if ch not in dict.keys():
dict[ch] = 0
dict[ch] = dict[ch] + 1
sortedItems = sorted(dict.items(), key=lambda x: x[1], reverse=True) import operator
str = "abcaaabcccaabrrcb"
dict = {ch: str.count(ch) for ch in str}
sorted_items = sorted(dict.items(), key=operator.itemgetter(1), reverse=True)
The dictionary comprehension syntax is new in Python 2.7 and 3k, but you can say dict((ch, str.count(ch)) for ch in str)
instead.reduce(lambda d, c: d.using({c:d.get(c, 0)+1}), "abcaaabcccaabrrcb", make_dict())
I don't know enough about HN to know why it doesn't display the two *'s that need to be be right after d.using(. I also couldn't figure out how to do keyword arguments in python where the key is a variable, not a constant, so if any python expert knows how, you could remove 4 characters from the above solution! :)
So where's the payoff?
A lot of people who are into FP simply find the concise style more satisfying. That's not regarded as a legitimate reason for preferring one choice over another, so this kind of discussion often hits a what-you-can't-say dead end. We've all seen too-clever code that was just awful, and we all keep saying that cleverness for its own sake is bad, and that position dominates the discussion in a heavyhanded way right now. Perhaps that's because it's necessary to keep the pseudo-clever crowd in check. I doubt it, though, because nothing keeps them in check.
Like it or not, the truth is that one factor driving what programmers do is intellectual satisfaction. This has always been and will always be the case, no matter how many schoolmistresses (most of them, of course, programmers trying to master their own temptations) beat us with the cane of "business value is the only thing that matters" and "no one cares about your stupid clever code".
(I'm by no means arguing that the impulse to make things "cute and concise" ought to take precedence or have free rein, but rather that this factor needs to have a seat at the table before we have a chance of working out a balanced approach.)
The mathematicians have one over on us here. In their world too, the most important thing is getting the job done, but elegance is a close runner-up: it's esteemed for its own sake, and more importantly it's well understood that there's an intimate dialectic between elegance and getting the job done. If we started thinking that way, we might finally make some progress out of these language game dead ends.
Switching gears to the technical matter of what the concise functional approach buys you (apart from elegance, for those who find it elegant), the trouble is that there's no way to answer this question at the level of a code snippet because all approaches yield reasonable snippets in intelligent hands. The real question is: what are the impacts of these varying approaches on building whole systems? That question, as far as I can tell, remains completely unaddressed! Except for the inevitable platitudes about side-effect free code being good for concurrency and so on. Yet that is the question that matters, and our code snippet debates are just the drunk looking for his car keys under the street lamp, even though he didn't drop them there, because the light's better.
Unless you believe that the choice of programming language is irrelevant (which is a little like saying an artist's medium is irrelevant), it's clear that different languages have different classes of programs that are easy to write in that language, and thus different classes of programs that will tend to evolve in it. Each version of an evolving program conditions the next version, so this influence gets compounded over time. (Code snippets are at the rudimentary extreme of that process, which is why they have so little comparative value; what we really need to see is how those snippets evolve into much larger systems.) The language itself and the current version of the program written in it condition the very stream of ideas about what you might do next. So there's a profound influence here, but we don't know how to study it. Failing that, the best approach is to let a thousand flowers bloom and see what systems emerge, but even there historical accident plays a huge role in where the development energy goes and thus what systems ever have a chance to emerge.
Personally I find that the imperative style is easiest in the small and the functional style is easiest in the large, and the trick is to find a design that allows the two to be fused with some regularity. But then I work in Common Lisp so I would say that.
Excuse the length, no time to make shorter.
I agree that I would like to see how functional approaches can benefit large systems, in concrete terms, since almost all of the examples I've ever seen involve micro-code like this article. I'm especially skeptical that it can make a dramatic difference for the kind of messy business code that today is usually solved by large object-oriented systems and modular libraries.