2 years of Clojure, and it still isn't clicking. What am I doing wrong?

It is possible that you’ve already reached the “zen state” of understanding-or-whatever through all your other experience, so there’s just nothing there that needs to click for you.

It’s all just different lipstick over the same pig, in the end, right? Regardless of how much functional programming you’ve done, if you bang on more mutable languages long enough, you’re eventually going intuit a semantic around what behaviors are more pure and which are more side-effecting.

Sure, there’s a lot of mutable, hot loop idioms that functional programmers have to “unlearn” from their mud slinging days, but those idioms are really a superset of pure-functional methods… mostly, I guess. Like, there’s probably C programmers out there that have been programming for so long, they’re like, “Yeah, I tend to program with partially monadic systems, I just don’t call them that.”

And I’ve seen some very veteran programmers transition to Clojure too, and they were already hip to a lot of the functional idioms, just from their years of experience, so they didn’t really take a long time to be very productive in Clojure. They also tended to love and prefer Clojure over their old langs, but that obviously doesn’t have to always be the case for all senior devs that can nevertheless be productive in Clojure.

Anyway, point being, clarity of state and functions that transform it is a mountain and Clojure is just one way to climb it and if you’ve climbed it another way and are as productive using those methods as you are in Clojure, then you might not need Clojure or see its benefits. Clojure just makes it so easy though :slight_smile:

i don’t think that’s fair to C programmers. It’s the Haskell guys that made imperative programming into a feature.

Lol

(This post must be at least ten characters)

Since no one’s said it, if getting comfortable in FP is part of the issue, The Little Lisper is worth considering. That’s what did it for me. It’s not enough–there are common Clojure idioms that don’t appear in that book, which uses Scheme. It won’t teach you about something like reduce, for example. But it’s a great starting point. (Don’t be put off by the apparently childish approach; it’s not childish. The “The Little X” books have a unique approach, unless someone else has now copied it.)

1 Like

Thank you! This is super helpful, I think what I am missing is the ease of editing. I will try Paredit. What IDE are you using?

1 Like

Thanks everyone, I think it really comes down to the editing experience which is not fluid. I understand FP and haven’t had any issues with other FP languages, just the actual editing experience is cumbersome.

I’m not the person you asked about IDEs, but I wanted to chime in with a “vote” for VS Code + Calva (+ Joyride to script VS Code in ClojureScript).

Calva includes paredit functionality plus LSP/clj-kondo for static analysis/linting. I’m a former Emacs/CIDER user and tried a lot of configurations over the years. Still, VS Code/Calva hits that sweet spot for me of a really great out-of-the-box experience that is also easily customizable (and it can sync settings/keymaps across machines automatically).

The combination of Calva’s custom REPL snippets and Joyride’s cljs scripting make it a very powerful environment!

3 Likes

Thank you so much! I’ve tryied using Calva before the problem I was having was making it play nice with the VIM keybindings. I can barely type if I don’t have my VIM bindings lol

Ah, yeah, I think I’ve heard some folks say the VIM keybindings interfere with Paredit keybindings… I never got into VIM… (despite a career stretching back over 40 years).

I use Cursive together with the IdeaVIM plugin. Had to learn emacs way back when, since that was the only editor with paredit, but I’m never going back to that. Nowadays parinfer might also be an option in some editors, which requires less keybindings but IMHO is also less powerful, but it might ruin other syntaxes less.

1 Like

Chiming in on parinfer, my impression is that it is quite detrimental to the beginner’s understanding, while the experience might be tolerable for some people who already have developed an eye for the code.

I’m a little late to the party, but I also use IntelliJ + Cursive primarily. I’m an old Emacs guy, but I write a lot of Terraform (and occasionally need to dip down into Java), so IntelliJ was worth the switch for pervasive autocomplete and navigation out of the box. It’s a beast of an IDE, though. I’ve only ever been able to get decent battery life running it since I upgraded to an M2 MacBook Pro this year.

I have a former client who swears by Spacemacs + Evil mode. Best of both worlds (and easily portable across platforms if that’s something you need). If you’re used to vim, this might be a good way to go.

I also hear good things about Clojure Sublimed. (I use Sublime mostly for HTML/CSS these days, not Clojure.)

Overall, you want to decomplect learning a new editor from learning a new language. Finding a way to work with Clojure in your preferred editor is better to start.

The key things your editor needs to be able to handle—

  • Structural editing so you can stop thinking about the parens and start thinking about expressions
  • Integration with the REPL so that you can build your programs interactively

REPL-driven development and structural editing are often the two a-ha’s that people who are struggling with Clojure (and Lisp, in general) are missing.

Once you see them, you can’t unsee them.

1 Like

There are paredit plugins for vim, though I haven’t tested them myself.

"What you ask is more a discussion thread. So I illustrate a few personal
moments, where I realised that Clojure is one good way of programming.
I assume you have some practice with Java or C++. Otherwise my answer
will not be so useful for you."

(comment
  
  "You may imagine that a standard example for Object Oriented programming
is a shape. Let's see how this looks in Clojure. This is a rectangle:"

  (def x {:width 12 :height 15})

  "You don't see that it is a rectangle, because my variable name is just x.
We normally don't hide things behind types or complicated language features.
We prefer good names. And quite often the rest becomes easy. But my
example is pretty small, so I continue with short variable names."

  "This is a colored rectangle:"

  (def y {:width 12 :height 15 :color "red"})

  "In case for some reason you want something like a constructur, you can
write it:"

  (defn make-rectangle [width height]
    {:width width :height height})

  "But here it is not really useful. It is nonsense code and we don't do
this. There may be other use cases for constructors. But here not." 

  "Let's do something useful."

  (defn draw-rectangle [rectangle]
    (str "drawing a rectangle"))

  (defn draw-colored-rectangle [rectangle]
    (str "drawing a rectangle in " (rectangle :color)))

  (draw-rectangle x)
  (draw-colored-rectangle y)
)

(comment

  "We want something 'like' object oriented behaviour. Let's do what
Java people do. They define a class. So our shape needs a type."

  (def x {:width 12 :height 15 :type-of-geometric-object ::rectangle})
  (def y {:width 12 :height 15 :color "red" :type-of-geometric-object ::colored-rectangle})

  "The shapes are still not of any specific type. We just agreed that
they now have a key :type-of-geometric-object. That is good
enough and even better and more expressive than what you normally
do in Java. And the name of the key does not mystify what we are doing."

  "Now we can define a method. Java developers know quite well what
I mean."

  (defmulti draw :type-of-geometric-object)

  (defmethod draw ::rectangle [rectangle]
    (str "drawing a rectangle"))

  (defmethod draw ::colored-rectangle [rectangle]
    (str "drawing a rectangle in " (rectangle :color)))

  (draw x)
  (draw y)

  "That was easy. And we did not need to define a class. It just works.
I can create any type and work with them whenever and however I
am in the mood."

  "People like something like inheritance. That idea makes sense. But
we don't have to think about inheritance. Inheritance is a strange
thing that comes from the world of computer science. What we really
want is to tell everybody that in our world a colored-rectangle is a
sort of a rectangle. And that is easy."

  (derive ::colored-rectangle ::rectangle)

  "And now all methods take this into account when dispatching.
You don't need things like base classes. It is just a small piece
of information that from now on all methods that fit for a rectangle
also fit for a colored-rectangle."
)

(comment
  "But why should we make all this so complicated? Colored shapes
have a color and shapes that are not a colored shape have no
color. It is so simple."

  (def x {:width 12 :height 15})
  (def y {:width 12 :height 15 :color "red"})

  (ns-unmap *ns* 'draw) 
  
  (defmulti draw (fn [shape] (if (contains? shape :color) :colored :not-colored)))

  (defmethod draw :not-colored [rectangle]
    (str "drawing a rectangle"))

  (defmethod draw :colored [rectangle]
    (str "drawing a rectangle in " (rectangle :color)))

  (draw x)
  (draw y)

  "In reality it may not be so easy. But here it is so easy. So we
don't waste our time doing it the complicated way. If it later turns
out to be more complicated we can still change our code. If you
don't have so much code, because you don't overcomplicate
things, you can quickly change these few lines of code if things
really get more complicaed than you initially thought. Premature
complicatedness normally does not pay off."
  )

(comment
  "Let's assume you want to give users a way to find out the color
of an object. Normally users of a painting program do not now
that they have to look something in a map or that they have to
call a method. So let us define a macro that makes these things
easy."

  (defmacro what-is-the [property of object]
    (assert (= of 'of))
    (list (keyword property) object))

  "Let's quickly test it"

  (what-is-the value of {:value 17})

  "Perfect, now we can use it"

  (what-is-the color of y)

  "That is a friendly API. Tha API talks to us. We don't have to
understand anything of the logic behind it. And if for some
reason we want to change the logic, because things get more
complicated than we thought first, we can change the macro.
We don't have to tell this anybody. So we don't have to adapt
tons of code."
)

(comment
  "We can do even more of this nice API."

  (defmulti answer-what-is-the (fn [property of object] property))

  (defmacro what-is-the [property of object]
    (assert (= of 'of))
    (list 'answer-what-is-the (keyword property) (keyword of) object))

  (defmethod answer-what-is-the :value [property of object]
    (:value object))
  
  (defmethod answer-what-is-the :circumference [property of object]
    (* 2 (+ (object :width) (object :height))))

  (defn now [] (new java.util.Date))

  (defmethod answer-what-is-the :date [property of object]
    (.format (java.text.SimpleDateFormat. "MM/dd/yyyy") (object)))
   
  "And here we go with a few examples."
  
  (what-is-the value of {:value 18})
  (what-is-the circumference of {:width 3 :height 5})
  (what-is-the date of now) 

  )

"If you want to do all this in Java you have to define tons of
classes and curley braces. And then you have simple problems,
that are still unsolvable. For example, you have a rectangle and
you want to give it a color. That is impossible in Java. And
even if you use langauges where this is possible (Python), it is
still a rectangle and not a colored rectangle. Objects cannot
change their type. But in clojure you can implemnt this if you
want. Java has exactly one answer how complex systems
have to be implemented. The answer is Object Oriented
Programming a la Java. And if you want something else, Java
is against you. While in Clojure you decide how you want to
implement things."

"If you explain all this to Java developers, they may laugh at you.
They tell you that you just play around with kindergarden examples.
That does not convince them. It is hard to believe that such simple
mechanisms are adecuate for the most complicated things. But
they are. And that is the click moment: 'Doing the same sort of
software out of much much simplier stuff.'"
3 Likes

Is it your code or someone else’s code that’s troublesome to read?

It kind of sounds like Clojure solves problems differently than the way your gut wants to solve them. Maybe it’s the LISPiness (since FP is not the issue for you).

Also, the way Java classes or objects are used in Clojure definitely looks kind of “reversed” to me, and may look that way to you too.

You may be doing a lot of translating in your head from Clojure to whatever language you’re most comfortable in.

I agree with your point about decomplecting new editor and language. I just use Nano for everything.