Dram is an Interactive Beginner's Clojure Guide in the Making

TL;DR; Help me with quality assurance of a new Clojure guide for beginners.

I am writing on a beginner’s guide to the basics of Clojure. My goals are that it should help people new to Clojure to

  • get started
  • quickly be able to read Clojure code
  • avoid (or at least recognize) some common pitfalls
  • know of resources for more information
  • get a tasty first sip of the Clojure REPL
  • like Clojure and its REPL and want more of it

These were not always the goals though. It started with me developing a (not yet released) feature of Calva where the user as quickly as possible can try Clojure Interactive Programming in an editor.

With only Calva + Java installed you will be able to start a REPL and have the editor connected to it, in a few seconds.

(Yes, not even clojure or Leiningen or anything else needed for this first step. Calva bundles @borkdude’s deps.clj for this.)

However, the users of this feature would be of mainly two types:

  1. Familiar with Clojure, and new to Calva
  2. New to both Clojure and Calva

For the latter group I started to write an intro to Clojure. Which seemed like a nice task and I should be able to ship the feature soon. Only that it rather is that I had started to dig a rabbit hole for myself.

I am still digging (writing). I have written 1218 lines so far. I like to write. The guide is taking shape.

I have no idea how much more I need too write. I need to find this out as quickly as possible. Can I dig myself out of this hole? I still think so.

This means that it will take a while before I can start focusing on the quality of the guide, in terms of correctness, suitableness and freeness from (most) spelling and grammar errors.

Here is where you come in, fellow Clojure advocates. Can I get some extra pairs of eyes from Clojure users of different levels on this? As a complete beginner you can see if this kind of guide helps in speeding up your learning. As an intermediate or expert you can help with seeing if the pitfalls you know of are covered, as well as help me identify the places where my misunderstandings of Clojure shine through.

The easiest way to test the guide out and be ready to post PR’s is to fork the dram repository, open the project in your Clojure editor of choice and start a REPL. Here’s the repo:

If you want to test the full experience with the coming Getting Started REPL feature, you can check out this pull request on Calva:

Either build a version of Calva from there (this is super easy, see How to Hack on Calva), or pick up the VSIX from Circle CI and install that in VS Code. As I know that Circle CI makes it unnecessarily hard to find the artifacts from the PR, here’s a VSIX link you can use:

(That VSIX will quickly get outdated, though, as @bpringe is releasing better Calva versions almost daily right now.)

You can also use the Calva command Start a Standalone REPL and paste the hello_clojure.clj code in there.

Thanks in advance for any help I can get with this. :heart:

14 Likes

Now added a section about functions, which was a glaring Todo-hole in the guide.

1 Like

And now added two sections:

  • Immutability
  • Functions for transforming data structures

Would love me some feedback on those, because extra important things!

1 Like

I’m a beginner, I haven’t done any serious programming for 20+ years. Here are my raw notes as I’m going through the tutorial. Let me say up front that I really like the format - my professional background is in standards development, so I’m nit-picky by nature :), hence the detailed comments.

On hello_clojure.clj

;; The way to use the guide is to read about the
;; concepts and evaluate the examples. Please feel
;; encouraged to edit the examples, and add new code
;; and evaluate that.

It is proposed to play with the code, but if I just change some values, the evaluation does not change automatically. I’m guessing I need to somehow refresh the evaluation?

;; (There are no statements.) Unless there is
;; en error when evaluating the expressions there
;; is always a return value

Typo (en->an)

;; Character types
  "hello"         ; string
  \e              ; character
  #"[0-9]+"       ; regular expression

CTRL+ENTER for evaluating regular expression leads to an error. Why?

map ; symbol
So the later example of map leads me to believe this is some kind of key-value pair? I think the symbols section should be explored a bit more, considering this is a newbie tutorial.

  ;; They compose

  {:foo [1 2]
   :bar #{1 2}}

The compose example needs a bit more explanation. What is the relevance of “:”? Overall, the syntax was never really covered, especially the reverse polish notation. This is important as the next section on lists with “proper functions at position 1” really requires the reader to get their head around this concept!

(*)
why does * evaluate to 1? What is it?

  ;; You define new functions and bind them to names
  ;; in the current namespace using the macro `defn`.

What’s a namespace in Clojure? Is this related to variables, do variables inside functions have their own namespace?

  ;; ... but that was a detour, back to special forms.
  ;; Official docs:
  ;; https://clojure.org/reference/special_forms#_other_special_forms

URL for the special forms includes an anchor, which does not fit with the context of the description (“official docs”).

(def foo :foo)
Does “:” have a special meaning here?
Why does (def foo foo) evaluate also to :foo?
Why does (= foo foo) and (= foo :foo) both evaluate to true?

;; This is what the macro `defn` does.
  ;; We can use the function `macroexpand` to see this:
  (macroexpand '(defn add2
                  [arg]
                  (+ arg 2)))

What are we trying to understand with the example of macroexpand? The line looks the same as above except for the “’” before the opening bracket and the “n” after def? I’m lost. (Edit: turns out this becomes clear at line 586).

Overall, clearly this prefix of a single quote (´) seems to be important but why?

(range 1 ; 10)
  ;; and then place a line comment so that the closing
  ;; bracket of that form gets commented out, the
  ;; structure breaks.
         )

I realize this is not to explain range but I’m guessing it returns a value up to but not including x?

;; == EXTRA SYNTAX ===
  ;; We've already seen the single quote
  'something
  ;; Which is, as we have seen, transformed to
  (quote something)

Ok, I’m officially lost on why we need the quote function? Is it a variable that holds as value its name?

(deref an-atom)
(type (deref an-atom))

Ok, I get the value out of a variable (of type atom?) But I’m more interested in the tooltip that talks about delays etc. Is this related to some timing issues - a value change is not instant? Also, type seems to be “PersistentVector” - tell me more!? Especially if “;; This is so common that there is shorthand syntax”.

@an-atom
  (= (deref an-atom)
     @an-atom)

So, the @-notation gives us access to the value of the variable but clearly this is not a normal variable as we’ve seen those before and we did not need the @-syntax…

#"reg(?:ular )?exp(?:ression)?"
Right, regular expressions are a big topic and cannot be covered here - perhaps a link to a more comprehensive source? And maybe explain what this particular regex does?

;; That hash sign shows up now and then, for instance
  #(+ % 2)

Hold on. You just told me that hash is used in front of regular expressions. This is not a regular expression. How many syntaxes start with a hash? … oh wait, you explain it further down:

  ;; The hash sign has a special role. It is aka
  ;; Dispatch. Depending on what character is following
  ;; it, different cool things happen.
  ;; In addition to sets, regexps and function literals
  ;; we have seen var-quotes in this guide

Maybe lead with this explanation?

(= #:foo {:bar 'bar
            :baz 'baz}
     {:foo/bar 'bar
      :foo/baz 'baz})

So we never really explained namespaces but I’m guessing we implicitly assign “foo” (or is it “:foo”?) namespace for the bar bar and baz baz key value-pairs? Is the “:” for key and “’” for the value? Then we compare the second kv pair that has explicit namespace declared via “:foo/”? Are namespaces of all variables automatically evaluated even if they are not explicitly declared in the evaluation? Does the linter keep track of these?

;; Unrelated to the #: There is another shorthand for
  ;; specifying namespaced keywords. Double colon
  ;; keywords get namespaced with the current namespace
  ::foo
  (= ::foo :hello-clojure/foo)

When we say “current namespace”, is that declared “globally” for the whole file or can you have some kind of “from this point on, all variables are in xyz namespace”?

  ;; #inst will convert the string it tags to an instance

  #inst "2018-03-28T10:48:00.000"
  (type *1)

So here I’m being confused - probably due to the overloaded term “instance”, is this only seen as instant - as in a moment in time (vs. instance of a object)? Or is it just a particular variable type of java.util.date (what exactly does that imply?)

(as a side note: macros, whoa! Very cool - it’s turtles all the way down!)

  ;; It is similar to the lexical scope of other
  ;; programming lannguages (even if this rather is

Typo

(for [x [1 2 3]
        y [1 2 3 4]
        :let [d' (- x y)
              d (Math/abs d')]]
    d)

Ok, in this case “’” has no reserved meaning, we can use “special characters” in variable names.

;; Filters and bindings can be used together.
  ;; Use both `:let` and `:when` to make this
  ;; comprehension return a list of all `[x y]` where
  ;; their sum is odd. The functions `+` and `odd?`
  ;; are your friends here.

I did not quite get this - why would I need the :let? The only solution I came up with was:

  (for [x [1 2 3]
        y [1 2 3 4]
        :when (odd? (+ x y))]
    [x y])

Anyway, it is a good idea to have little puzzles sprinkled in the tutorial.

  (as-> 15 $
    (range 1 $ 3)
    (interpose ":" $))

I think this is the first time we are using $ as a variable name. Maybe we should go over the allowed characters in variable names?

[Edit stops here - line 1000 or so, I’ll come back once I’ve reached line 1500]
(Side note: the file has many comment forms but their usage is never explained)

1 Like

Thank you, thank you, thank you! This is invaluable feedback. I will try to address the issues in the guide and will also probably come back here for some clarifications.

Feeling the urge to answer your questions here as well, even though I know they are mostly rhetorical. Let me just address this:

(def foo :foo)
Does “:” have a special meaning here?

Oh, yes. We should establish the role of the : in the syntax for keywords

Why does (def foo foo) evaluate also to :foo ?

Having evaluated (def foo :foo), foo ”becomes” :foo, so (def foo foo) is not really changing anything. Compare with let x = 2; x = x; in Javascript or similar language.

Why does (= foo foo) and (= foo :foo) both evaluate to true?

Again, because we just defined foo as symbolising :foo.

This is tricky to explain. I’d phrase it as ”it is a symbol for the variable that holds the value”. I still struggle with it myself… Let’s see what I can do to give more context to the quote special form.

At least it is an opportunity where I can remind about that symbol names can be using most characters. And mention that there is nothing special with $.

Maybe for as-> it’d be good to use a more “relatable” placeholder, such as x or foo, so people would know there’s nothing “special” about it?

(that being said, I like using <> myself :smiley: )

1 Like

Wouldn’t >< make more sense? :grinning_face_with_smiling_eyes:

Probably :smiley:

I think I have addressed most issues raised in this thread in the latest version. Quite a lot of new text added in the process.

Also, to make it easier to test this guide while it is being developed, I just released a public version of Calva that has this feature enabled. So for anyone that want to test it. See:

https://calva.io/getting-started/

1 Like