What do beginners struggle with?

Hello! I would like to find out what do beginners to Clojure struggle with. While learning Rust I discovered a few things there were giving me trouble and where I occasionally needed help from a mentor to make progress, which got me thinking - what are these trouble points in Clojure? I would very much appreciate your input, whether you are a beginner yourself or have experiences helping beginners.

While mentoring Clojure beginners at Exercism.io, I have noticed a few problems in their source code that show up repeatedly. I would like both to confirm these and expand to problems I have not noticed and problems beyond source code.

I have seen people struggling with the language basics (using the correct constructs at the correct place - (require ..) vs (ns .. (:require ..)), let vs def, etc), with not leveraging the platform and its native classes and methods, limiting themselves to Clojure itself (e.g. Character/digit), naturally limited knowledge of core functions and solving manually something that already had a solution in the core library, tendency to write low-level code with recur or reduce instead of leveraging the most suitable sequence functions. And I am sure they also struggle with tooling, going from error messages to fixing the cause and more.

What is your experience? Thank you!

12 Likes

Coming from typed languages I am struggling to find a good way to discover shape of data in a new codebase.
Sometimes I drop in the middle of code and it takes me several traversals up and down of call stack to understand what is assoced on the map and what is read from it. And this knowledge is lost in a couple of days because either I forget it or code has been changed by teammates.
Text fixtures and specs help in some cases but not every function has unit test or spec, only those on the boundary of a package.

6 Likes

The most common problems I’ve seen for beginners, as I’ve been helping them on Slack over the years:

  • “extra” parentheses, resulting in ClassCastException (…cannot be cast to clojure.lang.IFn)
  • Trying to use def for “local variables” (which you mentioned)
  • Thinking that for is like a C/Java loop
  • Assuming that functions like assoc modify the collection

I’d group all of the last three into “imperative/mutable thinking” which is, overall, the hardest shift to make for most beginners that I’ve seen. Taking away assignment and traditional loops is what throws off most new-to-Clojure folks who come from more “traditional” languages.

5 Likes

Is it there a right a wrong to all of those things you mentioned? If you have tests proving that something works and the things are all well-named, then sometimes it’s better to sacrifice speed and efficiency for readability imo especially if there is an unnoticeable difference in the end. For example, I don’t think there is a hand-down correct (require ..) vs (ns .. (:require ..)) because it depends on the project. You could be building any of the many things that run JVM Clojure, or Reagent ClojureScript, or some other Frontend ClojureScript, or ClojureScript on AWS Lambda, or GraalVm, or some other Clojure so it’s hard to say sometimes what’s the best if all the different ways technically work for what you need.

But yes, if you made tutorials / books / videos / anything about “common clojure refactorings” I think that would be pretty awesome for beginners. :slight_smile:

Often people coming from OOP languages think in terms of “create some helper classes and then call the functions on those classes to get some data” and up using lots of let blocks to hold local variables for things (at least I did / do lol). Then as you get it working (and with some deftests) you can refactor chunks around and sometimes get rid of the let altogether.

Another thing is when you are a beginner and only know a few tools, that’s all you know. Reading the api docs is like reading the dictionary. You can do it kind of, but it in order to really understand how the words should be used you need to encounter them in real life by just coding a lot of different Clojure programs, using each thing individually, and getting feedback from colleagues and exercism.io mentors. :slight_smile: :grinning:

Great points from @seancorfield too although I’ve found that now any editor, even the free ones, give much nicer matching parenthesis matching and color coding that a few years ago. I would hate to have to match up closing parens without that, and so the “plain-text” experience for writing Clojure isn’t fun, but if your text editor is setup properly then it should be.

1 Like

Just to be clear, I wasn’t talking about mismatched parens – I was talking about beginners who add extra matching pairs of parens around things where they don’t belong and then getting weird ClassCastException errors, e.g.,

((foo 42) "bar")

where (foo 42) returns an int or a string. There was an example on StackOverflow just the other day where a beginner had several constants def'd and then had () around each one of them in the code where they were used – the equivalent of (42) and (13) – but they hadn’t gotten that far because they already had extra parens around (if ...) and were getting ClassCastException from that returning a string that was then being “called” (and failing).

Another thing I’ve noticed beginner struggle with a lot is setting up and getting going. Just being able to get Clojure installed and running, with some editor, and connecting to a REPL.

Often, they struggle to pull in dependencies, don’t know where the things in a guide are from, like, core.async? Where is that? Don’t know what to require, but in this case, even if you do it isn’t enough.

This is even worse in ClojureScript. Even I struggle to get a ClojureSript setup going. Especially, knowing what to pull in that supports ClojureScript and not just Clojure.

I find in general, requires/imports confuse beginners for sure. Also, failing to find functions for what they are trying to do, could be as basic as how to add to a map. Especially if they are used to a static language, where they’d just rely on auto-complete to show them all possible operations on a type.

9 Likes

I’ve been learning Clojure for a few months now.

Things I struggled with or I am still struggling with:

Setup:
I am using spacemacs because I love vim keybindings but I still haven’f fully figured out
how to set everything up. The windows sometimes seem to jump around. I can’t get
clj-kondo to work. I have made changes to my ~/.lein/profiles.clj file but I still don’t get what
the difference between a plugin and a dependency is and where I can find them, or what
the newest version are without looking up the repos on github.

Missing Types:
I have used statically typed languages for >10 years now and it is hard to embrace
dynamic typing. Clojure makes it so much nicer to do like javascript but I still feel
like I am getting lost at times because I can’t quickly look at somethings type-signature.
True, having “only” lists, vectors, maps and sets makes it simpler but I still can’t wrap my head
around how I would build something “bigger” without the guidance and restrictions of types

Nullability:
I am primarily working with Kotlin which has checked nullability.
I have noticed that Clojure handles nil very gracefully in many cases but I
still can’t get over my desire to check it as soon as it might happen.
Is it ok to pass nil around? Should I nil check everywhere?

REPL Driven Development:
I absolutely love the quick feedback of the REPL but I’d love to
see a few videos of how people work and why they work that way.
I think that there are a lot of kinks in my workflow and it would be nice to
see how a pro uses the REPL

Naming:
Clojure.core takes up a lot of good names (i.e count) so now I can’t name a
symbol count without creating a name clash. Of course I can name it cnt but
that always feels a bit icky to me. Maybe it is something to just get used to, but
it is definitely hard for me.

What to build:
This is more a personal struggle that I also have with other languages but I thought it might
be worth mentioning. When I am interested in a new language I normally go through an online
quick start guide. If that’s interesting enough I buy a book and work through that.
Now I am doing a lot of exercises on exercism but it is unclear to me where I should go next.
I feel like I have to build bigger projects in order to really learn the pros and cons of Clojure but
I struggle with finding ideas for stuff to build.
Maybe it would be nice to have a “collection” of medium sized problems that clojure lends itself to solving?

That’s all I can think of for now, I’ll add more if anything comes to mind :slight_smile:

5 Likes

Lots to unpack here and it’s always good to hear directly what folks struggle with on their learning journey!

Setup: ~/.lein/profiles.clj – be careful here. The vast majority of problems I see new lein users encounter are due to various plugins they’ve added to their profiles.clj file. I know that’s what a lot of libraries/tools recommend(!), but it’s common to have added some plugin X for a particular project and then later have forgotten about it and it causes weird interactions/problems in some new project. As I often say: “Leiningen is easy but it is not simple” – lein does a lot of “magic” that makes it easy to get up and running but can cause you all sorts of pain until you understand exactly what it is doing (your comment about plugins vs. dependencies is exactly symptomatic of that and I totally feel your pain). I switched from Leiningen to Boot in 2015 and to the “built in”/official CLI/deps.edn in 2018 and I pretty much never touch Leiningen now.

Missing Types: I think folks are subjectively either fans of static typing or fans or dynamic typing. I’ve built a lot of big systems with both and I prefer dynamic typing. YMMV. Some people never get comfortable with dynamic typing.

Nullability: It’s idiomatic to rely on “nil-punning” so it is common to let nil pass around through the code. Just keep it away from numeric and string operations since they don’t like it :slight_smile:

RDD: I put a couple of videos up on YouTube showing how I work with Atom/Chlorine/REBL https://www.youtube.com/channel/UC8GD-smsEvNyRd3EZ8oeSrA (more are planned but it’s not a medium I’m comfortable with so it takes time and effort for me – I’m old-school and prefer everything in text form when I’m learning).

Naming: Naming is hard. Read Zach Tellman’s “Elements of Clojure” for some really good insight on this topic. It’s okay to “shadow” clojure.core names if it makes your code clearer but be careful about scope (small functions help reduce the potential conflict between your local symbol name and your use of a core function – you could always have count as a local binding and then explicitly use clojure.core/count as a function but…).

What to build: This is always a big puzzle for me when I’m learning a new language! My “go to” projects have always been a “contact manager” and/or a “to do manager” but beyond that I get stuck for ideas.

2 Likes

I didn’t have this in the wayback, but it would have helped.

https://clojuredocs.org/quickref

I came from a strong lisp background. Not using CLOS was (and still is) an issue for me. Stuff is stuck in my muscle memory.

From a typing standpoint, the inability to create rational information exchange definitions is a problem. Real typing (not implementation typing) is extremely powerful. And difficult to address in clojure. It some ways it is extremely good at it. In other ways, it’s horrible.

In lisp, premature optimization isn’t truly the source of all evil. People thinking that source code is documentation are. When you’re maintaining code, it is the best reference by far. When you’re using code written by someone else (or yourself years ago) it is the worst. It’s difficult to write and enforce interface contracts. While that’s true in many languages, I personally think it’s worse in clojure than many others.

I’m probably a bad person to write about this. I have somewhere between 30-70 languages under my belt. I’ve only disliked a handful of them. I’ve liked fewer. Much of the problem is where one is starting from. Starting in Smalltalk makes for an easier start than Java. OO languages are much easier to learn because functionality is naturally segmented in digestible chunks. People aren’t overwhelmed by everything at once. That’s definitely and issue for all of the lisp languages.

Haskell and clojure are the opposite side of the same coin. Clojure doesn’t enforce discipline, which means programmers are much more likely to write implementation code than code to the abstract problem. Both are good, both are bad. Scala is the coin run through a blender.

I suspect most young programmers are coming from python. Perl with words rather than characters. Not really, but that’s my big picture view of it. One can do anything easily, and most do. While one can write functional code with appropriate breakdown of responsibility, most don’t. Forcing yourself to do that is hard. A thousand times worth the price, but hard. It takes energy, interest, and I hate to say talent to do it.

I don’t remember who/when it was said, “Naming things is one of the two hard parts of programming.” That’s not it, but it’s kinda close. That saying has been around for four decades. It was also part of the reason boffins didn’t think COBOL could ever become a workable language.

Haskell, Scala, and many others are extremely proud of solving the null problem. They’ve magically given them a different name. Really a wrapper around something that might contain a null. It’s just as likely to cause a program failure with the benefit of writing extra lines of code for every bit of information. All of the lisp languages love nil. It rarely causes program failures, because most everything executes the correct fix-up behaviors without writing special code. Both a good and bad thing. Java forces one to identify every error in every function call. So no one does anything. Most of us love nil, most Haskel programmers are appalled, Java programmers are released, Smalltalk programmers jjust say “why are people getting excited?”

I like your response. My paraphrase “The hard part about learning clojure is finding all the reasons people like clojure.”

According to the State of Clojure 2019 Survey results, about 14% of Clojure developers came from Python (31% came from Java, nearly 14% came from JavaScript). We should have the results of the 2020 survey in the next week or so.

Clojure is easy to set up. No one will tell you how to do it. That’s super-secret information. Where there is reasonable documentation, it only applies to Unix. 90% or more of developers are on Windows. Too bad.* Many of the “helper” documents are years out of date. Always look for a dateline before reading the first sentence. Even that doesn’t help at times.

I agree that Clojure is set up. The REPL is incredibly easy to work with, but I prefer to work with IntelliJ with cursive.

I’m a super Clojure noob, and I find the hardest thing about the languages are in line with what lots of people have mentioned here: for example, functions like count, which I would usually use as a variable name.

I can never remember how to require namespaces, which is frustrating. I’ve been flipping back and forth between various Clojure books and none of them are very good: they tend to cover a bunch of concepts that you can’t put together until you get several chapters in. Any book worth its salt should have you programming very basic things within a chapter. The only one so far I have seen so far that has accomplished this is Clojure for the Brave and True, although it is definitely not for programming newbies since it throws you into some fairly complex concepts right off the bat, which I appreciated, having been programming for 37 years.

I’ve found that the best resource so far overall has been reading other people’s solutions on exercism; the language is fairly easy to understand if you have some FP background. I was fascinated to learn about --> and ->> when I first came across them, which I haven’t seen covered in any bools but that are so useful.

The parentheses, of course, are extremely difficult at first, but you get used to them quickly to the point that they are barely an issue.

I’m curious as to which books you’ve tried and whether those include Living Clojure and/or Getting Clojure?

I think it’s gotta be the lack of documentation for some of the libraries I use or want to use. I don’t particularly enjoy having to figure out what each function does or what the whole flow of the library is. I just wish every library maintainer had the time to at least document their libraries like this guy does on a consistent basis:

I also used to think tooling was hard but then I discovered Cursive (plugin for IntelliJ) which eliminated most of the time I spent trying to figure out how to set up a decent environment on emacs, vscode or vim. Ever since then, stuff like Calva came out and I also learned the basics of emacs for me to be productive in them but I just can’t leave Cursive behind for some reason.

I really agree with this. I think most people would find this boring and it might be painful to make one of these videos but I always learn things by watching how other people set up their development environment and use it.

Apart from lisp (and I don’t use format or loop…) and static html I’m a complete newbie.
Getting a cider REPL up with emacs from web instructions/debian was easy, example code evaluates OK. no difficulty there.

The newbie instruction books I’ve seen seem to have a background in java/javascript/python etc. as a prerequisite. Maybe I’ll read advanced skills books later.

I’m pretty sure anyone can figure out what each function/operator/macro requires and does, without cross references to unknown lanuages.

The difficulties are
Figuring out insider jargon.
What can be done with clojure (and only clojure)?
How does my code get to interact with my local webserver?
Will it interact with any hosting providers webserver?

Are there any clojure server-side “hello worlds” out there that would send me an email if I click a box on my webpage.

Not 100% on topic, but I’d like to share this really cool usage video. @Daniel_Szmulewicz works with the REPL, and constrains himself to one dependency. What he gets done is impressive!

Linking to the first of three videos.

2 Likes

Nice! And of course when I was not looking for it a good example from @seancorfield found me.

Thanks, Sean!

1 Like

The plethora of choices, and not knowing how to make them, particularly with the limited time I have to devote to learning Clojure. It’s a stupid thing, but I’ve been hung up here for a long time. I’ll read everything I can about Om - that was a long time ago. Then do the same with reFrame. Then Fulcro. Then I think that it would be better to build a traditional web app using server side Clojure. Then I read a ListCast newsletter, today, there’s a new course helping confused people like me start - with Reagent. Then it’s Luminous … ok, I’m GOING to start with Luminous … that’s decided - it’s the easiest way in. And then I think about how much I hate javascript build tools, and I start thinking … ClojureScript, stay with ClojureScript. And with the limited time I have, I keep skidding off the surface.

I know skipping around like this is a highly inefficient way to learn. I should be building skills from the ground up. But it is hard to choose from which point to start, then I get pulled away by other urgent work, and a few months go by, again …

5 Likes