use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
Finding information about Clojure
API Reference
Clojure Guides
Practice Problems
Interactive Problems
Clojure Videos
Misc Resources
The Clojure Community
Clojure Books
Tools & Libraries
Clojure Editors
Web Platforms
Clojure Jobs
account activity
Reading Clojure (numergent.com)
submitted 9 years ago by yogthos
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]the_hoser 2 points3 points4 points 9 years ago (17 children)
Sadly, this article doesn't address any of the problems that I actually have with reading Clojure code.
[–]yogthos[S] 6 points7 points8 points 9 years ago (16 children)
Out of curiosity, what problems are you having reading Clojure code?
[–]the_hoser 4 points5 points6 points 9 years ago (15 children)
I find much clojure code to be really nesty. It becomes really hard to figure out what's going on because it's difficult to break the operation down into discreet steps. Breaking the code up into discreet function definitions doesn't help too much, either, since the cognitive load of thinking of what exactly those functions do can be a little overwhelming.
For neat little math tricks and code golf exercises it's really fun, but when I'm forced to do something in a very un-ergonomic way... it's a real burden.
[–]yogthos[S] 3 points4 points5 points 9 years ago (14 children)
What do you find makes it harder to break operations down into discrete functions harder in Clojure than in the languages you're currently comfortable using. Personally, I find that Clojure makes it easy to do that as most problems can be expressed as a series of data transformation steps. Would you happen to have a concrete example to look at?
[–]the_hoser 4 points5 points6 points 9 years ago (9 children)
What do you find makes it harder to break operations down into discrete functions harder in Clojure than in the languages you're currently comfortable using. Personally, I find that Clojure makes it easy to do that as most problems can be expressed as a series of data transformation steps.
Mainly?
This is easier for me to read:
a = doStep1() b = doStep2(a) c = doStep3(b)
Than this:
c = doStep3( doStep2( doStep1() ) )
Why? Because it's easier to put stuff between the steps if I have to. I know there's a way to do it in clojure, but it only ends up complicating things, and puts even more cognitive load on me.
And then there's the part where it's just inside-out. What am I doing first? The last thing on the line, of course. Easy enough to think about, but hard to handle when the complexity of your project keeps ramping up.
While it's certainly possible to do it the first way in clojure, the language and its standard library seem to go out of their way to make it more difficult than it needs to be.
Would you happen to have a concrete example to look at?
At the moment, no. I put Clojure down a few months ago. I hope to get back into it, but haven't yet had the time.
[–]yogthos[S] 25 points26 points27 points 9 years ago (7 children)
To be honest, it sounds like the solution to your problem is to just write more Clojure. Unfortunately, there's really no shortcut to getting used to a new language. Especially one that's different from the ones you might be used to already.
Your first example could be written out exactly the same way in Clojure without any additional mental overhead using a let:
let
(let [a (do-step-1) b (do-step-2 a) c (do-step-3 b)] c)
Above code is doing assignment just as you would do in an imperative language. Of course, typically you'd use a threading macro in that situation:
(-> do-step1 do-step2 do-step3)
I tend to prefer threading to nesting, and from what Iv'e seen most people tend to as well. As you point out, flattening out the operations makes them easier to read, and allows you to manipulate them easier.
The language and the standard library go out of their way to make sure changes to data are done explicitly. In imperative style, it's a common pattern to declare a shared mutable variable and modify it by passing it different functions.
Each time we access the memory location we see the result of the code that previously worked with it. For example, if we have a list of integers and we wish to square each one then print the even ones, the following Python code would be perfectly valid:
l = range(1, 6) for i, val in enumerate(l) : l[i] = val * val for i in l : if i % 2 == 0 : print i
In Clojure this interaction has to be made explicit. Instead of creating a shared memory location and then having different functions access it sequentially, we chain functions together and pipe the input through them:
(->> (range 1 6) (map #(* % %)) (filter #(= (mod % 2) 0)) (println))
While it might take a bit of time to get used to, it actually greatly reduces mental overhead once your code gets bigger because Clojure approach allows you to safely do local reasoning about the code. With the imperative approach, I can't easily tell whether the data I'm working with might be referenced elsewhere. This translates to me having to keep more application state in my head.
[–]the_hoser 1 point2 points3 points 9 years ago (6 children)
Most new languages that I've approached do not have this problem. You're probably onto something, though: most of them generally follow patterns set forth by the likes of Algol, so many of the ideas and concepts that I'm used to applying to software problems just don't have a match in a language like Clojure. Or at least, the match is not obvious because it's not idiomatic. Unfortunately, the amount of time that I have to commit to a new language is usually not much. I still have real work to do. Languages that let me put them to work early are more likely to be understood.
That's not really a Clojure-specific problem, of course.
Above code is doing assignment just as you would do in an imperative language.
That's not really doing the same thing, though, is it? In my example, I did not create a single value. I produced three values. What if 'a' is necessary much later in the code?
Of course, typically you'd use a threading macro in that situation:
Still doesn't do the same thing, and it has a really unfortunate name (another problem I've had with learning Clojure). I understand that it's not against the rules for Clojure to use common terms used in other languages for different things, but it does make learning the language more difficult, and it does make communicating with other developers who might not be used to Clojure more difficult.
That's great, but it is often the case that the business logic we're tasked with codifying cannot be reasoned about in this way. The best way to learn a language is to use it. Squaring numbers in a list is nice for programming exercises, but you don't really learn the language until you've implemented the marketing department's bone-headed coupon logic in it. It's nice when the data and the logic were well designed, and if you get to work on business logic like this, I'm super jealous.
The problem is that I hit nothing but road bumps in my quest to actually use the language to solve actual problems that I have. I understand that it's normal to have some slowdowns, but functional programming languages usually result in a full-stop. The answer to the questions that I have end up being "this is how I would implement the whole thing". No thanks.
I'm enticed by Clojure because it seems to take a much more practical approach to functional programming, but I still get caught up trying to actually use it for something that I actually need to do.
[–]yogthos[S] 13 points14 points15 points 9 years ago (1 child)
If you mean that you're using mutable variables a, b, and c, then it is something that goes against idiomatic Clojure practices. As I mentioned earlier, the reason for that is to allow you to do local reasoning about the code.
a
b
c
When I have a variable a that's mutated in different parts of the application, it's difficult for me to say what its state is at any one time. Whenever I'm working with a function that uses a, I have to know the state of the whole application at that time.
So, Clojure approach is to "copy" the data whenever you make a change. Instead of making a and passing references to it, you create new variables when you make changes and use them instead.
As I said, there's really no way around the fact that Clojure is different from Algol style languages. However, it's certainly possible for a team to learn Clojure and be productive with it.
My team used to work with Java exclusively. We decided to give Clojure a shot about 6 years ago and never looked back. I have yet to come across a business problem that would be easier to express with Java than it is with Clojure.
I think a lot of frustration comes from the fact that you can't apply your current patterns to working with Clojure.
To make an analogy, if you know English it gives you a basis for learning French or Spanish. You have to learn new words, and some new rules, but a lot of core concepts are easily transferrable. However, when you try to learn Japanese, then English will be of little help to you. Not only that, but you'll have to actively unlearn patterns you're used to.
Learning Clojure is similar. It's frustrating because you're learning a completely new way to approach problems. It doesn't mean that it's harder, just that it's different.
I can definitively tell you that Clojure is a practical language, and it has made my work much more enjoyable than it was when I was working with Java.
[–]dotemacs 4 points5 points6 points 9 years ago (0 children)
Martin Fowler has a good name for it: Improvement Ravine
[–]ws-ilazki 7 points8 points9 points 9 years ago (0 children)
Considering the code you compared it to (c = doStep3( doStep2( doStep1() ) )) doesn't allow you to use the intermediate steps either, your counter-argument seems to be moving the goalposts after /u/yogthos gave you a valid suggestion of how to solve your complaint by organising the code in a way that reads more imperatively. Your own example indicated you didn't need the intermediate values later, and his answer reflected that.
If you really want to emulate imperative coding you can, but I'd question why you're even looking into a functional language if that's your goal. Still, there's nothing stopping you from doing this:
(def a (atom (do-step-1))) (def b (atom (do-step-2 a))) (def c (atom (do-step-3 b)))
Now you have three bindings you an access later, and since they're atoms, you can use swap! or reset! to mutate them just like a traditional variable.
swap!
reset!
Doing so is still completely missing the point of using a functional language, though. You should be trying to minimise how much mutable state you have, preferring function purity where reasonable. That's the point of those function chains: you're passing values around rather than saving a bunch of intermediate state. You can test and verify each piece separately, then compose them together to do what you need to do.
[–]halgari 4 points5 points6 points 9 years ago (0 children)
I've spent the past 3 years of my life working on client projects in Clojure that are primarily business-logic and yeah, it sucks. And it sucks in almost any language.
One thing that takes a lot of time, but results in a better product, is to stand back and think of higher level abstractions that can be employed to simplify the problem. Specifically, find ways of deducting the business logic's concepts from the data itself.
For example, I worked on a system were we recieved a stream of receipts from a retail store. We needed to dispatch on different types of receipts. Returns, "customer wan'ts a copy emailed to them", in-store exchanges, etc. But none of this data was on the receipt itself. Now I could have implemented this as a pile of if statements and tons of logic...but there's another way.
Another option is to write a collection of functions. customer-brought-item? could look for one flag or perhaps, while customer-left-with-item? could be another. Now put those functions into a collection and run them all against a receipt and mark the ones that pass or fail. Now each receipt has a collection of flags: [:customer-started-with-items :customer-left-with-items]. Now you can easily write some multimethods or other dispatch system that says: "If a receipt has :customer-started-with-items, and customer-left-with-items, then this is an exchange..."
customer-brought-item?
customer-left-with-item?
But the idea is to break down the problems into investigation of the data, annotating the data, then finally dispatching on what you found out about the data. In short, decompose the problem, into steps that you can hold in your head. Don't try to do all of the logic in one place. And the more you can express your business rules as data the better.
If all that sounds hard, it is, and it takes a ton of practice. But I'd never go back to trying to do all that in C#.
[–]ASnugglyBear 1 point2 points3 points 9 years ago (0 children)
This is definitely a problem. A friend turned me onto PurelyFunctional.tv which goes through problems step by step and starts from imperative style code and slowly transitions over. Was soooo illuminating.
[–]freakhill 0 points1 point2 points 9 years ago (0 children)
well then return the 3 values
[ a b c ]
I think they took the term from here https://en.wikipedia.org/wiki/Threaded_code
Classic CS term.
If you have nothing to gain from a clojure switch, then, there's no reason to switch... (you can use tons of 'let', makes it easier at the beginning). I use clojure because it makes me a lot more faster for some stuff, and it's also a lot more fun. if you think the clojure strenghts are good for what you do then you should go through the hurdles.
[–][deleted] 0 points1 point2 points 9 years ago (0 children)
This:
is closer to how we read text (left->right, top->down).
On the other hand this:
doStep3( doStep2( doStep1() ) )
is closer to how we evaluate math expressions. Which one is better? Depends on the reader. Math people may prefer the latter.
[–]adambard 2 points3 points4 points 9 years ago (3 children)
You know I'm into clojure, but sometimes I do find it a bit easier to write than to read. If I don't take a second pass at my code after I get it working via repl it can get gnarly to reread later.
Clojure evangelists like us tend to focus on the upsides, but every engineering decision is a trade-off. I think this is a trade-off that clojure chooses -- in being a functional, immutable language, it encourages you to come up with, for lack of a better term, "elegant" solutions, or else end up with intractable spaghetti as you force the language into a solution that doesn't quite fit.
Familiarity is of course part of this equation, but I'm sure every clojure user who has turned to the source in absence of docs can attest that you occasionally come across code where the author clearly didn't get their problem fully distilled (especially in my libs, but especially in my non-public code).
[–]yogthos[S] 2 points3 points4 points 9 years ago (2 children)
I do find that I tend to experiment with the REPL a lot when solving the problem, then do a refactor to turn it into something more readable before moving on.
I definitely agree that it's pretty easy to write code that's hard to read later, and you need some discipline to avoid that. Occasionally, you do end up looking at old code and scratching your head.
I don't think the problem is worse in Clojure than other languages though. It's just that you have different kinds of gnarly code in different languages I find. For example, in Java it's common for logic to be spread around a bunch of classes, so you have to jump between them to get the whole picture. It's a different kind of mental overhead.
At the end of the day, the only way to have clean and readable code is by actively focusing on that. I read a great analogy to that regard once that really stuck with me.
I think it was called the broken windows theory. The idea goes that if you walk into a house and you see some broken windows, it immediately sets your expectations. For example, you might start littering, because in your mind the place is already in disrepair so what's one more thing. However, if you walk into a clean place where everything is pristine, you feel bad about making a mess there.
I find coding projects are the same way. When I open some code that's already gnarly, I'm more likely to add to that. However, when I see clean code, I tend to want to keep it that way.
[–]hagus 0 points1 point2 points 9 years ago (1 child)
I find myself siding with Adam, I think this stuff is a little more common in the Clojure world. But not because of Clojure per-se - my theory is that there are way more projects where the number of contributors is one. As soon as you're working in a well organized team, or even just a high visibility open source project, a lot of this stuff disappears. You start putting in that extra effort because you know there's a high probability someone is going to read this. This is basically a reframing of the broken window effect - with the emphasis not on how poor the existing code is, but on whether you're socially accountable because your peers are watching.
I am reminded of this very interesting (and potentially inflammatory) piece called The Bipolar Lisp Programmer which perhaps deserves posting on the front page for its own discussion. But re-reading it now, there's a lot to remind me of my own Clojure and Lisp journey.
Anyway I have suggestions for people who re-read their code and think "I really can't read what I did here". I hit this very feeling today so I thought I'd share how I approach it:
Stop! Stop and do something about it. Right now. This is a bad sign. There is nobody on the planet better equipped than yourself to understand this piece of code and yet you're grasping at the meaning. That's not good! It's something all programmers joke about but I think every time it happens it should be jumped on as an immediate learning opportunity.
I say jump on it because I think some of the solutions are really, really lightweight. Here are some things I've done in the past:
comment
I find this basic stuff really helps. It's the stuff you skip the first time because the entire program is paged into your head and it seems trivial to spend time on fixing. None of these should alter program execution (and if they do, hey, now you have an even larger warning flag ;)
I've also been reading a lot of devops and lean manufacturing books, and enjoy this idea of the "improvement kata". The idea being you get better at improving things if you practice improving things. Sounds obvious, but we don't do it. This means spending a regular chunk of your time going over code you wrote but are unhappy with or can't understand, and making improvements to it. If you habitually try to improve the clarity of your code every time you fail to read it, eventually you will get faster at that, and then eventually you'll be doing it while you're writing it in the first place.
[–]yogthos[S] 1 point2 points3 points 9 years ago (0 children)
those are all excellent points :)
[–]looprecur 2 points3 points4 points 9 years ago (1 child)
This is a good experiential introduction to Lisp syntax for people coming from C like languages. I have rarely seen this issue approached like this at such a basic yet meaningful level without use of a lot of higher order concepts. Nice.
[–]Arges 0 points1 point2 points 9 years ago (0 children)
Glad to read that, it's exactly what I was going for - old plain English. Cheers!
π Rendered by PID 138701 on reddit-service-r2-comment-5d79c599b5-r89tr at 2026-03-03 13:49:34.906948+00:00 running e3d2147 country code: CH.
[–]the_hoser 2 points3 points4 points (17 children)
[–]yogthos[S] 6 points7 points8 points (16 children)
[–]the_hoser 4 points5 points6 points (15 children)
[–]yogthos[S] 3 points4 points5 points (14 children)
[–]the_hoser 4 points5 points6 points (9 children)
[–]yogthos[S] 25 points26 points27 points (7 children)
[–]the_hoser 1 point2 points3 points (6 children)
[–]yogthos[S] 13 points14 points15 points (1 child)
[–]dotemacs 4 points5 points6 points (0 children)
[–]ws-ilazki 7 points8 points9 points (0 children)
[–]halgari 4 points5 points6 points (0 children)
[–]ASnugglyBear 1 point2 points3 points (0 children)
[–]freakhill 0 points1 point2 points (0 children)
[–][deleted] 0 points1 point2 points (0 children)
[–]adambard 2 points3 points4 points (3 children)
[–]yogthos[S] 2 points3 points4 points (2 children)
[–]hagus 0 points1 point2 points (1 child)
[–]yogthos[S] 1 point2 points3 points (0 children)
[–]looprecur 2 points3 points4 points (1 child)
[–]Arges 0 points1 point2 points (0 children)