My little story of "What do beginners struggle with?"

It’s a follow-up to this thread What do beginners struggle with?
This story wouldn’t be written without the great help of @PEZ - thank you very much!

I’m a 26-year-old female software engineer. I spent half a year in my current company, writing both Clojure and ClojureScript and fighting with React Native and Android. Before that, I spent 2 years learning half-time. My first job was a freelance project for a Canadian Company. I was using HTML, Sass, and touching pieces of Angular.js. Other than that, I used JavaScript and Python.

I decided to check out Clojure because I was learning JavaScript, struggling with all the quirks and I asked my partner (who was and still is a Clojure Developer) - “why didn’t you tell me to learn Clojure if it is so great?”. He said that it’s not a Junior-friendly language and that there are not many Junior-level resources or jobs out there. But I’ve decided to try it despite all that. I installed Visual Studio Code - I’ve heard that this is the best editor for Clojure, mostly because of the Calva integration.

My partner showed me Clojure Koans - I solved them with a little bit of his help. The first problem was of course learning to read LISP syntax because it was the very first time for me. Although it wasn’t as hard as I expected at the beginning. The parenthesis was also a problem. I used to (which is not good practice, but I was a noob) add parenthesis in JS while I was not absolutely sure about the sequence of actions (in for example math actions - in Clojure it’s clear, in JS you need to remember which one is stronger. Or in logical statements…) - so I added them in places, where I wanted to be sure it’s gonna be evaluated first. Now it’s very clear, but back then it was a bit confusing and caused a lot of mistakes. Then I keep asking “How can I change anything if data is immutable? It’s a crazy idea! Can I just make everything an atom please?” I’ve been struggling with this concept for a very long time, probably because of my previous experience with Python and Javascript.

Other things I’m struggling with in general:

  • Shorthand functions - hard to catch for me
  • Regex is still magic, but that’s not Clojure-specific
  • Recursive functions were a horror for me, now it’s a bit better.
  • Another hard thing is refactoring - deciding when it should be refactored and when it’s good enough. I have a huge problem with removing my code because every time I’m very proud of what I wrote.

I get most of the tips from my partner, but also from the Clojurians Meetups. The most important tip was to just start coding and learning in the meanwhile. I wanted to read 2-3 books first, but they encourage me to create projects and to learn by coding and reading documentation mostly.

AfterClojure Koans, I start using Clojure for real projects as everyone recommend - so I created a word-counter - an app for the terminal ([https://github.com/EwaTrzemzalska/word-counter]), and then check-weather ([https://github.com/EwaTrzemzalska/check-weather]) with the use of Weatherstack API, and a similar app for air-quality checking ([https://github.com/EwaTrzemzalska/air-quality]). I started reading “Clojure for Brave and True” (I never finished this book, which I’m ashamed of). I was also using clojuredocs.org very often.

I really like that Clojure is well-organized and that it has great and clear documentation. One of my favorite Clojure things is that most of the basic functions already exist, just waiting for me to be used. I like function-oriented programming and I feel like everything is less messy than JavaScript.

Later, I attended ClojureBridge in London, where I built my first Single Page app and it was my first contact with ClojureScript. Next, the pandemic started and it wasn’t the best moment to look for a Junior Clojure job - actually, it’s still very hard. I started a Nanodegree program at Udacity - Full Stack Web Developer in Python. I learned a lot and was about to start looking for a Python offer, but then I accidentally found a job posting for Mid ClojureScript Engineer, I applied and solved the recrutation task. It reminded me how much I like this language compared to Python. I did not get the job, but I switched to Clojure again, for good - I finally found my first full-time Clojure job shortly after, and now I’m learning the language every day. I’m reading “Learn Clojurescript”, and searching for functions for my project at clojuredocs. I’m working a little bit with my partner and he helps me big time, teaching me good practice and showing some solutions and paths - unfortunately, in my job, there isn’t anyone else except the usually-busy-with-other-stuff CEO, who knows the language, and I’m the most senior engineer despite being a complete newbie. But I guess we all have to start somewhere.
Thank you for reading that!

31 Likes

Thanks, @EwaTrzemzalska! This is pure gold. Personally I learn much better from hearing a story like this than from some ”binary” yes/no answer type of thing.

It is super inspiring too, how you stayed on track and gave it the new shots that was needed in order to land yourself a Clojure job. Congratulations!

This part is fun and funny :laughing: (and also just very interesting and important). I usually think of this parenthesis problem as something to do with moving the opening paren one notch to the left. Something people who pick up Clojure quickly figure out, and something people who don’t want to pick up Clojure use as an excuse. You present a dimension to it that I have not considered before: In math we use parenthesis for nailing the evaluation order and we can use as many as we want to make it clear for ourselves what the result should be. Infix languages support this quite perfectly. Clojure goes boom!

Again thanks! I’ll have tremendous use of this story when tweaking my attempts to guide beginners in Clojure land.

4 Likes

Congrats on your Clojure journey! And thanks for the experience report.

Can you expand a little on this point?

Shorthand functions - hard to catch for me

Can you go into this a little deeper, about what aspects were harder than others? Thanks!

1 Like

Thanks!
About shorthand functions - when I’m thinking or reading the code I like reading it like a book
(fn [name surname] (str "Hello" name surname)) - here I see that I’m gonna greet a person with name and surname.
#(str "Hello" %1 %2) - here I need to remember what was the first and second argument, it’s just one more thing to keep in mind. Named parameters are just easier to think about for me (for now at least).
But to be completely honest I don’t really see the point where the shorthand function is gonna be really useful and better than fn syntax.

6 Likes

Thanks, you were the first one who was brave enough to ask more questions until I was able to put it into words!

1 Like

Same here. For several reasons, this one being extra important:

The shorthand function syntax does not carry its cognitive weight, imo. You only save a few characters, while you loose in having to keep more things in mind.

Even when you don’t have parameters, or are not using them, the full fn is clearer. Take some event handler:

  :on-foo #(println "foo!")

vs

  :on-foo (fn [_e] (println "foo!"))

The latter is telling ”Hey, I know there’s an even object coming in here, I don’t care about it.”. We can also note here that clj-kondo will see the prefixing _ and interpret it as ”so you don’t care, obviously”.

Also with #() you’ll soon run into problems when you can’t nest them and wasting time on trying to figure out why you get Invalid arity: 0 trying to just return vector or a map.

I didn’t know this when I started to learn ClojureScript, so I still have the bad habit of sometimes using the shorthand function syntax, but I am improving in avoiding it.

We should probably include the advice to avoid this syntax early on in the onboarding of Clojurians.

3 Likes

Me too. I’ve seen (and agree with) a style recommendation to switch to named arguments whenever a %2 or greater is required. There are one or two exceptions (such as a reversed compare) but it’s a good rule of thumb.

2 Likes

Great stuff! It took me a while to land my first clojure job, so don’t worry too much.
And also take into account that even if you don’t use clojure in your job, the fact that you learned it will make you a better developer in whatever language you use. Keep up the good work.

As for the shorthand syntax, sometimes it’s useful when it’s the kind of code you don’t care much, eg: I’m defining some flags for tools.cli, where you can pass a parse function:

["-t" "--threads THREADS" "Number of threads"
    :id :threads
    :parse-fn #(Integer/parseInt %) ;; << convert from string to int
    :default (.availableProcessors (Runtime/getRuntime))]

Personally I think the shorthand is fine in this situation

1 Like

I’ll start off by noting that the shorthand function syntax is implemented as a “reader macro”. Once parsed, it will generate the fully expanded form. e.g. for:
#(str "Hello" %1 %2)
After parsing the compiler will be given something similar to:
(fn [p1__444# p2__445#] (str "Hello" p1__444# p2__445#))

In other words, there’s no technical difference. It’s entirely up to whether the programmer thinks it makes things easier.

In general, I think it’s great to name function arguments, though I do think there are a few occasions where it’s useful:

  • Fixing arguments in a function. For instance, if all my queries will be against a database, then a shortcut doesn’t seem to add much cognitive load:
    (let [my-q #(q % the-database)] (my-q the-query))
    I can see the argument against this though.
  • Using a simple Java interop function where a function reference is required:
    (map #(Long/parseLong %) ["1" "2" "3"])
  • It creates faster code than using partial:
    (map (partial + 2) [2 3 5 7 11])
    is essentiall the same as:
    (map #(+ 2 %) [2 3 5 7 11])
    But faster (since partial allows multiple arguments)
    Again, there is an argument to use (fn [x] (+ 2 x)) but in this case the “x” is as arbitrary as “%”, and it’s taken more typing.

All of this is about using a simple % argument. I almost never use the numbered variety. About the only time I have is when I want to swap the order of the arguments:
(let [flip-f #(f %2 %1)] (flip-f :a :b))

But as I said at the start, if you don’t think it works for you, then don’t use it. Clojure doesn’t care :slight_smile:

3 Likes

I think this is a good example of why the shorthand can be misleading – these are not equivalent: the first form is a 0-arity function and is not equivalent to the second form which is a 1-arity function.

I will only ever use the shorthand for a 0- or 1-arity function, and even then not always. For 2-arity or more, I use (fn [..] ..) or a named local or private top-level function.

3 Likes

In ClojureScript you get away with using both, disregarding the difference. I exemplified the case “or are not using [the parameter]”. My point is that even when the shorthand form gets this much shorter, with less stuff, I think the fn special form wins, because clarity.

Whoa! Really? ClojureScript doesn’t do arity checking? Hmm, well, I guess JS doesn’t…?

But, yeah, even with that “allowance”, it’s a good example of why being explicit with fn can be better even for 0- and 1-arity functions! :slight_smile:

1 Like

Ya, it makes ClojureScript a bit more difficult to refactor, since you can’t just break the arity to check what no longer compiles.

On that note, I have to say I’ve slowly started to use: #(println "foo!" #_%) for these kind of scenarios. I don’t super like it, but its convenient. I have not yet committed such code, since I don’t know if this is truly intended behavior, but at the repl and when toying its handy.

1 Like

I think it is not so much about wether it works for someone or not. It’s more like ”one more thing to sort out” for a beginner. I have put some little more context around it in the Welcome to Clojure guide, for now. Hopefully I’ll find some more ways to prepare the reader for this encounter. Your usage examples comes in super handy in those efforts!

It really depends on how you look at it. As with most things, and especially with programming, there’s this principle of complimentarity where any given purpose or axis of efficiency has some associated corollary. In this case there’s the discipline of point free programming.

One of the most common sources of programming errors is premature naming. Why give a thing a name if it doesn’t need one? Especially if it already had one?

In this example, we’re creating new names:

(let [{new-name1 :x new-name2 :y} {:x 1 :y 2}]
  ...

This example reuses existing names:

(let [{:keys [x y]} {:x 1 :y 2}]
  ...

Tons of new names to keep track of:

(fn [please don't forget me]
  (let [[a b c d] [(inc please) (inc don't) (inc forget)]
    (println :so-much-context/where-am-I? [a b c d])))

No names:

#(println :context-free/i-don't-care [%1 %2 %3 %4])

Sometimes, though, we want to define new names to refer to existing things simply as a form of communicating to other developers the contextual meaning of things. This is great until we get into areas where naming isn’t easy. And sometimes we over contextualize things prematurely, when a piece of code should have been left more general.

The ability to bind new names to things gives us awesome powers of abstraction and the ability to communicate meaning to other developers, but with that comes a a cost of typos, errors and the parochialisation of otherwise more general abstractions.

That being said, I’m not advocating for ‘no new names anywhere’ - just that there are other concerns involved as well and sometimes, for certain goals, being too explicit can work against you.

2 Likes

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.