text: Clojure promises unprecedented productivity. Its devs boast top salaries.
  It's true! But before you ever get to that point you'll face unprecedented confusion.
  Understanding Clojure's tooling is very challenging.
  Most guides push you straight into writing Clojure and don't bother explaining its complex foundations.
  This is different.
  I will NEVER tell you to go do something yourself.
  You'll be able to complete this guide 100% uninterrupted on any recent Windows machine.
  ZERO knowledge required! Nothing will be installed permanently!
  We'll get java, discuss java stuff (JARs, the classpath, Maven), play around in Clojure's REPL, install lein, explain what lein really does, and make a simple Clojure program that counts most frequent words in dev.to headlines.
  Follow along carefully, don't skip any parts, faithfully execute every command as I do!
  Copy-and-paste PowerShell commands1, but when evaluating Clojure please retype yourself!
  In only 10 minutes you'll be spared weeks of frustration.
  Open up a PowerShell (press
  Windows key, type
  powershell then press
  Enter). Please, don't close it till the end.
  Ok, let's get started!
  What is Clojure
  Clojure's homepage has this to say:.
  I hope you find Clojure's combination of facilities elegant, powerful, practical and fun to use.
  Rich Hickey
  author of Clojure and CTO Cognitect
  But what is it really?
  On the most fundamental level the Clojure runtime and compiler is just a Java program.
  Before we begin we need to get
  java.
  Getting Java
  We will get ourselves an OpenJDK implementation of Java 11.
  Even if you already have
  java, please follow along. Not going to affect your existing installation.
  In PowerShell:
  PS C:\Windows\system32> cd ~ PS C:\Users\adas> mkdir clojure PS C:\Users\adas> cd clojure # 187MB download PS C:\Users\adas\clojure> wget -OutFile java11.zip # might take a minute or two to download. OpenJDK is almost exactly like Oracle's JDK, # for our purposes there's no real difference. # The JDK (Java Development Kit) includes all the necessary binaries to work with java, # like java, javac, javah, javap and so on... Writing web request ... .... # load up .NET stuff for extracting Zips into PowerShell (no need to understand) PS C:\Users\adas\clojure> Add-Type -AssemblyName System.IO.Compression.FileSystem # we extract the zip we just downloaded PS C:\Users\adas\clojure> [System.IO.Compression.ZipFile]::ExtractToDirectory((Join-Path $PWD "java11.zip"),$PWD) # rename the extracted directory to java11 PS C:\Users\adas\clojure> mv jdk-11.0.1 java11 # we don't need the zip anymore PS C:\Users\adas\clojure> rm java11.zip PS C:\Users\adas\clojure> java11\bin\java -version # make sure the binary works openjdk version "11.0.1" 318-1-16 # it does ... # temporarily update our environment PATH variable so we can access java everywhere PS C:\Users\adas\clojure> $env:path+=";" + (Join-Path $PWD "java11\bin") PS C:\Users\adas\clojure> java -version # did it work? openjdk version "11.0.1" 318-1-16 # it did ...
  Great,
  java is ready.
  Getting Clojure
  Now we can finally get ourselves Clojure. Like most Java programs it ships as a JAR.
  JARs are basically ZIP files.
  Most code and code-like stuff in the Java world is distributed as JARs.
  So let's get a JAR for Clojure.
  PS C:\Users\adas\clojure> wget -OutFile clojure.jar # let's take a quick peek inside the jar # (notice how we use a function for extracting Zip Files, because JARs are just Zips) PS C:\Users\adas\clojure> [System.IO.Compression.ZipFile]::ExtractToDirectory((Join-Path $PWD "/clojure.jar"), (Join-Path $PWD "jar-disassembly")) # we extracted the jar to jar-disassembly, normally you don't ever manually extract a jar # but we really want to see what's inside # let's see the general dir structure PS C:\Users\adas\clojure> tree jar-disassembly ... ├───clojure │ ├───asm │ │ └───commons │ ├───core │ │ ├───protocols │ │ └───proxy$clojure │ │ └───lang ... └───META-INF └───maven └───org.clojure └───clojure # now let's see full paths PS C:\Users\adas\clojure> tree /F jar-disassembly ... │ │ PersistentHashMap$INode.class │ │ PersistentHashMap$NodeIter.class │ │ PersistentHashMap$NodeSeq.class │ │ PersistentHashMap$TransientHashMap.class │ │ PersistentHashMap.class │ │ PersistentHashSet$TransientHashSet.class │ │ PersistentHashSet.class │ │ PersistentList$EmptyList$1.class │ │ PersistentList$EmptyList.class │ │ PersistentList$Primordial.class │ │ PersistentList.class ... # interesting, mostly a lot of .class files # this is JVM's (Java Virtual Machine) standard format for bytecode # also note how everything is nested in directories # as we'll later see directories are very important in the Java world # to make use of our jar we need to put it on java's "classpath", it's like our system's PATH but for java PS C:\Users\adas\clojure> java -cp clojure.jar ... Usage: java [options] <mainclass> [args...] (to execute a class) ... # but this didn't do anything, we have to indicate a class <mainclass> to run PS C:\Users\adas\clojure> java -cp clojure.jar clojure.main Clojure 1.8.0 user=> "Nice, clojure.main starts a Clojure REPL! (Read, Eval, Print Loop)" "Nice, clojure.main starts a Clojure REPL! (Read, Eval, Print Loop)" user=> "Let's Ctrl-C out of here" # Actually JAR's META-INF/MANIFEST.MF will usually specify a default Main-Class, here clojure.main PS C:\Users\adas\clojure> cat jar-disassembly/META-INF/MANIFEST.MF Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Created-By: Apache Maven Built-By: hudson Build-Jdk: 1.6.0_20 Main-Class: clojure.main # so if we do -jar instead, java will automatically run Main-Class - clojure.main PS C:\Users\adas\clojure> java -jar clojure.jar ... user=> "Back to the Clojure REPL" "Back to the Clojure REPL"
  user=> 50 ;; <= this is a number literal 50 user=> (+ 50 1) ;; this invokes function + with arguments 50 and 1 51 user=> (type 60) ;; a Clojure number is just a java.lang.Long java.lang.Long user=> (def v ["a" "vector"]) ;; square brackets denote vectors #'user/v ;; this is a #'var, we'll learn what vars are later user=> v ["a" "vector"] user=> (type v) clojure.lang.PersistentVector user=> (first v) ;; first returns the first element of the vector "a" user=> (nth v 0) ;; if you want to access by an index "a" user=> (v 0) ;; but this is weird, a vector itself is also a function! A function just like "nth". Interesting. "a" user=> (second v) "vector" ;; now this is a "map" literal, it's very similar to JavaScript's object literals user=> (def m {:key "value", :another-key "almost like js right?"}) #'user/m user=> m {:key "value", :another-key "almost like js right?"} user=> (type m) clojure.lang.PersistentArrayMap ;; but a key, unlike javascript, isn't just notational sugar, ;; a key, keyword actually, is a first-class value type in clojure user=> :key :key user=> (type :key) clojure.lang.Keyword ;; Clojure maps take any basic Clojure values as keys, they don't have to be keywords user=> {1 "val"} {1 "val"} user=> {[1 2] "val"} ;; even vectors work {[1 2] "val"} user=> (get m :key) ;; get takes a collection and a key and returns a value "value" user=> (m :key) ;; like vectors, maps are also invokable as a function, this is roughly equivalent to (get m :key) "value" user=> ({[1 2] "val"} [1 2]) "val" user=> (:key m) ;; :keywords are also invokable as a function! this is also roughly equivalent to (get m :key) "value" ;; but other simple types aren't, you cannot invoke a number as a function ;; this is one of the reasons why :keywords are the preferred type for map keys user=> (1 {1 "val"}) ClassCastException class java.lang.Long cannot be cast to class clojure.lang.IFn user=> (first {:key "value"}) ;; so what's the "first" element of a map? [:key "value"] ;; a vector containing the first key/val? user=> (type (first {:key "value"})) clojure.lang.MapEntry ;; well, almost user=> (first (first {:key "value"})) ;; works a lot like a vector would :key user=> (key (first {:key "value"})) ;; but we can do this :key user=> (val (first {:key "value"})) ;; and this "value" ;; let's talk about symbols now user=> 'a-symbol ;; this is a "quoted" symbol a-symbol ;; why did we "quote" it? user=> a-symbol ;; if we don't Clojure will try to "resolve" the symbol and retrieve it's value CompilerException java.lang.RuntimeException: Unable to resolve symbol: a-symbol in this context user=> m ;; m doesn't throw because we've previously defined m {:key "value", :another-key "almost like js right?"} user=> (resolve 'm) ;; we can manually resolve m #'user/m user=> (resolve 'a-symbol) nil ; nil as in nothing - we cannot resolve a-symbol to anything user=> (type #'user/m) ;; so what is #'user/m clojure.lang.Var ;; a var user=> 'user/m ;; this is a symbol too user/m ;; what's this "user" part? ;; user is a namespace, the "namespace-qualified" symbol user/m has namespace "user" user=> (namespace 'user/m) "user" user=> (name 'user/m) ;; and name "m" "m" user=> (namespace 'm) ;; a bare symbol "m" isn't "namespace-qualified", hence nil nil ;; namespaces are a very important concept in clojure, everything exists in a namespace user=> *ns* ;; special symbol *ns* contains the current namespace #object[clojure.lang.Namespace 0xf8908f6 "user"] ;; this is why we're seeing this "user" thing in our REPL indicating the current namespace user=> (ns other) ;; we jump to a different ns nil other=> m ;; m is undefined here CompilerException java.lang.RuntimeException: Unable to resolve symbol: m in this context other=> user/m ;; we could still access the old m by namespace-qualifying our symbol {:key "value", :another-key "almost like js right?"} other=> (ns user) ;;ok back to user ; libraries in Clojure will come in their own namespaces, Clojure ships with some built-in ones user=> (require 'clojure.string) ;; unloaded namespaces have to be required first, like clojure.string nil user=> (ns-publics 'clojure.string) ; let's check what clojure.string defines {ends-with? #'clojure.string/ends-with?, capitalize #'clojure.string/capitalize, reverse #'clojure.string/reverse, join #'clojure.string/join, replace-first #'clojure.string/replace-first, starts-with? #'clojure.string/starts-with?, escape #'clojure.string/escape, last-index-of #'clojure.string/last-index-of, re-quote-replacement #'clojure.string/re-quote-replacement, includes? #'clojure.string/includes?, replace #'clojure.string/replace, split-lines #'clojure.string/split-lines, lower-case #'clojure.string/lower-case, trim-newline #'clojure.string/trim-newline, upper-case #'clojure.string/upper-case, split #'clojure.string/split, trimr #'clojure.string/trimr, index-of #'clojure.string/index-of, trim #'clojure.string/trim, triml #'clojure.string/triml, blank? #'clojure.string/blank?} ;clojure.string/split looks cool but how do we use it? user=> (doc clojure.string/split) ; thankfully the REPL has a doc function that'll help clojure.string/split ([s re] [s re limit]) Splits string on a regular expression. Optional argument limit is the maximum number of splits. Not lazy. Returns vector of the splits. user=> (clojure.string/split "A string we want to split into words" #" ") ;; #"regex" <= this is a regex literal (think /regex/ in js) ["A" "string" "we" "want" "to" "split" "into" "words"] user=> (require '[clojure.string :as s]) ; to save ourselves typing we can require a namespace under a shortened name nil user=> (s/split "A string we want to split into words" #" ") ["A" "string" "we" "want" "to" "split" "into" "words"] user=> (resolve 's/split) ;but it will still resolve to the full name #'clojure.string/split user=> (require '[clojure.string :as s :refer [split]]) ; require split directly into our namespace nil user=> (resolve 'split) ; still nicely resolves to the real deal #'clojure.string/split user=> (require 'main) ; what if we require a non-existent namespace? FileNotFoundException Could not locate main__init.class or main.clj on classpath. clojure.lang.RT.load (RT.java:456) ;; interesting, so java was looking for main__init.class or main.clj on the classpath ;; so what if there actually was main.clj on the classpath? ;; let's add it, unfortunately this means we have to Ctrl-C out of here ;; create main.clj - we explicitly set the encoding because some (like UTF16) may cause issues PS C:\Users\adas\clojure> Out-File -Encoding ASCII main.clj PS C:\Users\adas\clojure> ./main.clj ;; should open main.clj in your text editor, or it might ask you to pick a program ;; if you don't have a text editor just pick Notepad, it'll do for now
  In
  main.clj:
  ;; C:\users\adas\clojure\main.clj remember to save :) (ns main) (def nine 9)
  Back to PowerShell:
  ;; now we'll also add current directory (".") to the classpath so java can find main.clj PS C:\Users\adas\clojure> java -cp "clojure.jar;." clojure.main user=> (require 'main) nil user=> main/nine 9 ; great!
  Don't quit the REPL, but let's change our file a little bit:
  ;; main.clj again, remember to save (ns main) (def nine 9) (println "hello world")
  Back to the REPL:
  user=> (require 'main) ; hmmm where's our println? nil user=> (require 'main) ; a namespace is loaded only once, requiring twice does nothing nil user=> (require '[main :reload :all]) ; but we can :reload :all to trigger a re-evaluation hello world ; nice! nil
  Remember, we want to count dev.to headlines' most frequently occurring words.
  A web scraping library would come in handy. After a bit of googling enlive seems like a good choice.
  But how do we get it? Onto the next part...
  lein
  Does Clojure have an
  npm equivalent (or
  pip or
  gem)? Kind of.
  lein is a lot like
  npm, but again - to understand we have to go back to the java world.
  Java already has an arguably more powerful build and dependency management tool - Maven2.
  Like
  npm, Maven can ingest our dependency specs and download relevant artifacts - usually JAR files containing code.
  But just like the
  java command, Maven's cli interface -
  mvn isn't easy.
  That's where
  lein comes in3.
  It will fetch dependencies with Maven, generate a classpath and run
  java, so we don't have to manually craft a very complex
  java command by hand. And much, much more!
  Getting lein
  As per leiningen.org/#install for windows we need to place lein.bat on our
  PATH:
  PS C:\Users\adas\clojure> wget -OutFile lein.bat PS C:\Users\adas\clojure> ./lein.bat self-install # lein will install itself Downloading Leiningen now... PS C:\Users\adas\clojure> ./lein.bat # should work now ... PS C:\Users\adas\clojure> $env:path+=";" + $PWD # temporarily add it to our PATH so it's available everywhere PS C:\Users\adas\clojure> lein # make sure it's available now ... # we create "project.clj" - it's a lot like npm's "package.json" PS C:\Users\adas\clojure> Out-File -Encoding ASCII project.clj PS C:\Users\adas\clojure> ./project.clj # should open project.clj in your default editor
  In
  project.clj:
  ;; C:\Users\adas\clojure\project.clj (defproject devto-words "0.0.1" :dependencies [])
  This defines a project
  devto-words version
  0.0.1 with no dependencies.
  First we'd like to pull in Clojure itself, right?
  We need to add
  [org.clojure/clojure "1.8.0"]4 to our dependencies.
  clojure and
  "1.8.0" makes sense, but why the
  org.clojure namespace?
  When lein interprets our dependencies it will use the namespace
  org.clojure as a Maven
  groupId, name
  clojure as
  artifactId and
  "1.8.0" as
  version.
  If we go to Maven Central's search page5 and look for "clojure" you can confirm that there is indeed such an artifact, a full xml spec is given:
  <dependency> <groupId>org.clojure</groupId> <artifactId>clojure</artifactId> <version>1.8.0</version> </dependency>
  This is what lein will understand the dependency vector
  [org.clojure/clojure "1.8.0"] to mean.
  Enlive's github README already gives us a lein-style dependency vector:
  [enlive "1.1.6"]. Great!
  So if we add Clojure and enlive, our project.clj should end up looking like this:
  ;; C:\Users\adas\clojure\project.clj (defproject devto "0.0.1" :dependencies [[org.clojure/clojure "1.8.0"] [enlive "1.1.6"]])
  By default
  lein adds
  src to java's classpath, having your code under
  src is a standard practice, so:
  PS C:\Users\adas\clojure> mkdir src # make directory src PS C:\Users\adas\clojure> mv main.clj src/ # move our main.clj to src/, remember to close main.clj in your editor PS C:\Users\adas\clojure> src/main.clj # should open main.clj at it's new location
  Now when we start the REPL with
  lein it will first download our dependencies from Maven Central et al.5, put them on java's classpath, and finally start the REPL:
  PS C:\Users\adas\clojure> lein repl # like I promised, lein is downloading dependencies first Retrieving org/clojure/clojure/1.8.0/clojure-1.8.0.pom from central ... nREPL server started on port 56786 on host 127.0.0.1 - nrepl://127.0.0.1:56786 ... REPL-y 0.4.3, nREPL 0.5.3 Clojure 1.8.0 OpenJDK 64-Bit Server VM 11.0.1+13 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e ;; you'll see there's a lot more output when starting this REPL ;; by default lein loads nREPL - a much more feature-packed REPL ;; but for now we don't need to know much about this user=> (require 'main) ;; still works hello world nil ;; confirm that we have enlive on our classpath as per user=> (require '[net.cgrand.enlive-html :as html]) nil ; we do!
  Without exiting the REPL let's make some changes to
  main.clj:
  (ns main) (require '[net.cgrand.enlive-html :as enlive]) (require '[clojure.string :as s]) ;; let's require ourselves a pretty printing function - pprint ;; we'll be looking at a lot of data, might get messy (require '[clojure.pprint :refer [pprint]]) (def url "")
  Back to the REPL:
  user=> (require '[main :reload :all]) nil user=> (ns main) ; let's set our namespace to main nil main=> url "" ;; oops we actually wanted https not http, change it in main.clj main=> (require '[main :reload :all]) ;; reload our changes nil main=> url "" ;; ok, all is good now ;; this will fetch the page and parse it (let's not concern ourselves with how it works) main=> (def document (enlive/html-resource (java.net.URL. url))) #'main/document ;; if you really want to see what's in there (pprint document), it'll print A LOT of stuff ;; again how this works is beyond the scope of this guide ;; but it's basically like running document.querySelectorAll(".single-article h3") or $$(".single-article h3") on a webpage ;; it should get us all the right headline elements from dev.to main=> (def headers (enlive/select document [:.single-article :h3])) ;; again (pprint headers) if curious main=> (first headers) {:tag :h3, :attrs nil, :content ("...")} main=> (first (:content (first headers))) "\n Freelancing 11: How to get started\n " ;; what a nasty string ;; let's put the string in a variable so it's easier to work with main=> (def sample-headline (first (:content (first headers)))) #'main/sample-headline main=> (s/split sample-headline #"[\n ]+") ["" "Freelancing" "11:" "How" "to" "get" "started"] main=> (s/split sample-headline #"[\n :]+") ["" "Freelancing" "11" "How" "to" "get" "started"] ;; uh we don' want that "" ;; but we're also not smart enough to make a better regexp ;; let's figure out a hack... ;; an identity fn returns it's input, basically does nothing main=> (identity 3) 3 ;; filter applies a function to every element in a collection ;; and removes any elements that return falsy values (think Array.filter in JavaScript) main=> (filter identity [1 2 3 nil false ""]) (1 2 3 "") ;; note that both nil and false are "falsy" values in Clojure main=> (not-empty "string") ;; not-empty returns it's input if it's not empty "string" main=> (not-empty "") ;; but returns nil if it is nil main=> (filter not-empty (s/split sample-headline #"[\n :]+")) ("Freelancing" "11" "How" "to" "get" "started") ;; if you're confused a javascript equivalent for above would be: ;; " Freelancing 11: How to get started".split(/[\n ]+/).filter(x=>x) empty strings are falsy in js so no need for not-empty ;; instead of doing (first (:content (first headers))) ;; we can compose the two functions into one with comp main=> ((comp first :content) (first headers)) "\n Freelancing 11: How to get started\n " ;; now we're doing the same thing for all headers with map ;; map applies a function to every element of a collection ;; and returns a new collection with each result main=> (pprint (map (comp first :content) headers)) ("\n Freelancing 11: How to get started\n " "\n \n The UX design pyramid with the user needs\n\n " ... ;; let's turn our piece of code that splits strings into words into a function ;; we create function tokenize that takes one argument s main=> (defn tokenize [s] (filter not-empty (s/split s #"[\n :]+"))) #'main/tokenize ;; verify that it still works (remember you should be retyping!) main=> (tokenize " a test sentence ") ("a" "test" "sentence") ;; it does! ;; so now for every header we call :content, then call first, and then tokenize main=> (pprint (map (comp tokenize first :content) headers)) (("Freelancing" "11" "How" "to" "get" "started") ("The" "UX" "design" "pyramid" "with" "the" "user" "needs") ("Why" "Bandwidth" "Still" "Matters") ("Using" "API" "first" "and" "TDD" "for" "your" "next" "library") ("7" ;; now let's flatten these word lists into one main=> (pprint (flatten (map (comp tokenize first :content) headers))) ("Freelancing" "11" "How" "to" ;; we're very lucky, clojure already comes with a function frequencies ;; it returns a map, mapping values to the times they occur in a collection main=> (pprint (frequencies (flatten (map (comp tokenize first :content) headers)))) {"Interviews" 1, "Why" 1, ...} ;; our code is getting really ugly, so many function applications don't look pretty ;; Clojure has a ->> "thread last macro" ;; we can use it to rewrite in a way reads a little more naturally main=> (->> headers (map (comp tokenize first :content)) flatten frequencies pprint) {"Interviews" 1, "Why" 1, ...} ;; it works, but what's going on??? ;; ->> is a macro, for complete docs see () ;; we can can examine what a macro expands to by doing macroexpand: main=> (macroexpand '(->> headers (map (comp tokenize first :content)) flatten frequencies pprint)) (pprint (frequencies (flatten (map (comp tokenize first :content) headers)))) ;; exactly like before! ;; note how we had to quote the argument to macroexpand with ' ;; quoting stops any evaluation from happening, so we can treat this piece of code as data ;; don't worry if it's still confusing, eventually it'll become second nature ;; so now all we need to do is sort our results ;; again, clojure already has a neat function - sort-by (detailed docs) main=> (->> headers (map (comp tokenize first :content)) flatten frequencies (sort-by val) pprint) (... ["Gift" 2] ["in" 2] ["In" 3] ["for" 3] ["5" 3] ["a" 3] ["A" 3] ["your" 3] ["to" 3] ["of" 4] ["How" 4] ["the" 5]) ;; yay this is what we wanted all along, ;; we could improve our tokenize to exclude words like the, a, of... ;; but we'll leave this as an exercise for the reader
  Let's clean up our
  main.clj now that we're almost done:
  ;; C:\Users\adas\clojure\src\main.clj (ns main) (require '[net.cgrand.enlive-html :as enlive]) (require '[clojure.string :as s]) (require '[clojure.pprint :refer [pprint]]) (def url "") (defn tokenize [s] (filter not-empty (s/split s #"[\n :]+"))) (def document (enlive/html-resource (java.net.URL. url))) (def headers (enlive/select document [:.single-article :h3])) (def top-words (->> headers (map (comp tokenize first :content)) flatten frequencies (sort-by val))) (defn print-top-words [] (doseq [w top-words] ;; doseq is like forEach in javascript (println (key w) (val w)))) (defn -main [] ;; we will explain this shortly (println "Will print top words in a sec...") (print-top-words))
  In REPL
  main=> (require '[main :reload :all]) nil main=> (print-top-words) Interviews 1 ... ;NICE! Ctrl+D out of the REPL
  Let's tell
  lein that main is our entry-point namespace:
  ;; C:\users\adas\clojure\project.clj (defproject devto-words "0.0.1" :dependencies [[org.clojure/clojure "1.8.0"] [enlive "1.1.6"]] :main main)
  By convention the main function of a namespace is
  -main (which we've fortunately just added).
  Now:
  PS C:\Users\adas\clojure> lein run #this will load main and run main/-main now Interviews 1 ...
  Final
  main.clj cleanup:
  (ns main ;; checkout to understand what happened here (:gen-class) (:require [net.cgrand.enlive-html :as enlive] [clojure.string :as s])) (def url "") ;; turn everything into functions so we only fetch stuff when actually calling print-top-words rather than on load (defn fetch-document [] (enlive/html-resource (java.net.URL. url))) (defn tokenize [s] (filter not-empty (s/split s #"( +|\n|:)+"))) (defn get-top-words [] (->> (enlive/select (fetch-document) [:.single-article :h3]) (map (comp tokenize first :content)) flatten frequencies (sort-by val))) (defn print-top-words [] (doseq [w (get-top-words)] (println (key w) (val w)))) (defn -main [] (println "Will print top words in a sec...") (print-top-words))
  PS C:\Users\adas\clojure> lein run # make sure it still works Interviews 1 ... PS C:\Users\adas\clojure> lein jar # we can build a jar of our code ... Compiling main Created C:\Users\adas\clojure\target\devto-words-0.0.1.jar PS C:\Users\adas\clojure> java -jar C:\Users\adas\clojure\target\devto-words-0.0.1.jar # we can't run this jar because it contains just our code, Clojure itself isn't included Exception in thread "main" java.lang.NoClassDefFoundError: clojure/lang/Var at main.<clinit>(Unknown Source) Caused by: java.lang.ClassNotFoundException: clojure.lang.Var) ... 1 more # instead we can build a fat jar containing everything needed to run our program PS C:\Users\adas\clojure> lein uberjar ... Created C:\Users\adas\clojure\target\devto-words-0.0.1-standalone.jar # should work as expected now PS C:\Users\adas\clojure> java -jar C:\Users\adas\clojure\target\devto-words-0.0.1-standalone.jar # lein install installs our project into the local Maven repo (usually located at ~/.m2/repository) # allowing us to depend on [devto-words "0.0.1"] in other projects on our machine PS C:\Users\adas\clojure> lein install ... Wrote C:\Users\adas\clojure\pom.xml Installed jar and pom into local repo. # to see a complete dependency tree for our project: PS C:\Users\adas\clojure> lein deps :tree [clojure-complete "0.2.5" :exclusions [[org.clojure/clojure]]] [enlive "1.1.6"] [org.ccil.cowan.tagsoup/tagsoup "1.2.1"] [org.jsoup/jsoup "1.7.2"] [nrepl "0.5.3" :exclusions [[org.clojure/clojure]]] [nrepl/bencode "1.0.0"] [org.clojure/clojure "1.8.0"] # notice how enlive itself pulls in 2 dependencies # also we never asked for clojure-complete and nrepl # lein's default "profile" pulls in some extras # we can rerun deps :tree with a profile "provided" which should only have essentials PS C:\Users\adas\clojure> lein with-profile provided deps :tree [enlive "1.1.6"] [org.ccil.cowan.tagsoup/tagsoup "1.2.1"] [org.jsoup/jsoup "1.7.2"] [org.clojure/clojure "1.8.0"] # just what we asked for # let's see what a bare classpath looks like PS C:\Users\adas\clojure> lein with-profile provided classpath # mostly makes sense # but we like to double-check everything PS C:\Users\adas\clojure> java -cp ' main Will print top words in a sec... # ok it works! # note how this time instead of running class clojure.main we ran main # this corresponds to the main namespace we worked on (clojure.main would start a REPL like before) # if you noticed, in our final cleanup we added (:gen-class) to the ns form # this makes sure a corresponding Java class is generated for our namespace # the last lein feature we'll explore is "new", it generates scaffoldings for new projects. PS C:\Users\adas\clojure> lein new my-project Generating a project called my-project based on the 'default' template. The default template is intended for library projects, not applications. To see other templates (app, plugin, etc), try `lein help new`. PS C:\Users\adas\clojure> tree /F my-project ... │ .gitignore │ .hgignore │ CHANGELOG.md │ LICENSE │ project.clj │ README.md │ ├───doc │ intro.md │ ├───resources ├───src │ └───my_project │ core.clj │ └───test └───my_project core_test.clj # this is what a proper Clojure project looks like
  Thank you
  We didn't learn much Clojure.
  But we learned many little details that most won't guides won't teach you.
  Now you can continue learning with confidence. You already understand the confusing parts, congrats!
  Look at footnote 6 if you want to make this installation permanent.
  Thank me
  If you enjoyed this let me know by 💬commenting💬, ❤liking❤, ⭐starring⭐ on github, or by 👣following the author on twitter👣.
  Otherwise I won't know and you'll never see a similar guide from me again 😭
  Links & footnotes
  learning materials
  - Clojure cheatsheet - always have this open, the function you're looking for is probably on this list
  - ClojureDocs.org
  - Official Clojure getting started docs
  - Enlive quickstart
  community
  - Clojurians slack - if you have a problem they'll probably help you out if you ask
  - ClojureVerse - a Clojure forum, beginner friendly
  lein
  editors & plugins
  - Clava - a Clojure extension for VS code
  - Cursive - a dedicated Clojure IDE, probably the best out-of-the-box experience
  - Cider - Emacs Clojure IDE
  Misc & Java-related
  - What is Maven?
  - Clojars repository
  - Official docs on java's classpath
  - Official docs on the many commands that ship with the JDK
  - Official docs on what JARs really are
  - A tutorial on the PowerShell ZipFile stuff
  footnotes
  1 Yes, these PowerShell commands are weird. But they enable this guide to work even on the most basic Windows machine. No extra software needed. ↩
  3 There are efforts to move away from lein, towards more lightweight solutions.
  See this. But from a learner's perspective they suffer from the same shortcomings. Knowledge of the java ecosystem is assumed. Whether you end up using lein or something else, all the lessons you learn here apply. And for the time being you'll mostly see people use lein.↩
  4 You probably want to use Clojure version
  1.10.0 (current Stable Release) in a real project.
  This guide uses
  1.8.0 as newer versions depend on clojure.spec which isn't bundled in Clojure's JAR. Downloading clojure.spec and adding it to the classpath would add unnecessary complexity to this guide.
  As you learned
  lein does all that for you. But for consistency we still used
  1.8.0.
  So if you change
  1.8.0 to
  1.10.0 in
  project.clj it'll work just fine.
  To reiterate - use
  [org.clojure/clojure "1.10.0"] next time.↩
  5 Maven Central is Maven's main repository.
  But unlike npm, the Maven world relies less on a single repository.
  In fact most Clojure libraries are hosted on clojars rather than Maven Central.
  Even cooler, when you use Maven, your machine also has its local Maven repository.
  You can install artifacts into your local repo and it works just like remote ones.
  lein install will do exactly that.↩
  6
  If you want to make our java and lein installations permanent, move
  java11 to a better location like
  C:\java11 and
  lein.bat to, say,
  C:\lein\lein.bat then add
  C:\java11\bin and
  C:\lein to your PATH.
  Don't know how to do that? See this example:
  # you need to open this powershell prompt as an Administrator PS C:\Windows\system32> cd ~ PS C:\Users\adas\clojure> PS C:\Users\adas\clojure> mv java11 C:\java11 PS C:\Users\adas\clojure> mkdir C:\lein PS C:\Users\adas\clojure> mv lein.bat C:\lein\ # should open the right windows menu for you # Select Path under System Variables, click Edit then add C:\java11\bin and C:\lein PS C:\Users\adas\clojure> rundll32 sysdm.cpl,EditEnvironmentVariables # restart powershell to reload PATH.... # make sure java and lein works PS C:\Users\adas> java PS C:\Users\adas> lein # don't need this anymore PS C:\Users\adas> rm clojure
  Credits
  Andy Fingerhut for spotting crucial errors and making great suggestions.
  Discussion
  Minor change needed (I guess maven has upped security), need to use https
  wget -OutFile clojure.jar
  Thank you! Updated.
  This is an amazing guide!
  I'm using and teaching Clojure since 1.5.1, but on Mac and Linux and with
  bootand lately with
  tools.deps.
  I haven't used Windows in decades, but I'm pleasantly surprised how viable is it to work with Clojure on it these days.
  In the future, your article will be the 1st I recommend to my friends who are inclined to learn Clojure.
  Thanks!
  In Clojure this ability to interactively figure things out at the REPL scales to really big projects.
  Say in js, you'll test out simple expressions at the REPL/console, but sooner or later you'll have to restart your program or trigger some kind of reloading.
  And just to be clear, you don't literally copy into or type at a prompt, usually you work in a source file and press a hotkey to evaluate expressions.
  For me, personally, it's not as much about being 10x more productive or whatever. But instead of getting distracted and giving up I can actually get something done sometimes thanks to this immediate feedback that keeps you engaged 😂
  Explaining why exactly it scales in LISPs, and even more so in Clojure while it doesn't in other languages is pretty dense, so I'll say this much - the LISP part has to do with homoiconicity, while Clojure's contribution is discouraging statefulness and encouraging pure functions.
  Macros are the only other advantage that I'd consider "objective". But again, a huge rabbit hole.
  The error-handling thing you talk about is probably about stacktraces being hard to read. You'll be dealing with Java stacktraces and exceptions (and JavaScript's for ClojureScript). It's often hard to understand what really went wrong.
  Even in this guide you'll see something like:
  We're trying to invoke a number as a function here. You have to understand a Clojure number is actually
  java.lang.Longand
  clojure.lang.IFnis Clojure's class for invokable stuff. Oh and the entire concept of casting classes. So imagine how confusing that must be to people who are new to both Java and Clojure. And even for veterans, this context switching is mentally exhausting.
  The other part of the error-handling criticism is probably from old time Lispers. In something like Common Lisp or even Emacs Lisp you don't get a stacktrace, you get backtrace, which shows you what each and every form along the way evaluated to. Hard to explain. But really powerful. And Common Lisp also has this thing called restarts which gives you a lot of control over error-handling, again really dense. But, anyways, from their perspective Clojure is just worse than other LISPs.
  Practically speaking I think the biggest con is Clojure is a hard sell for anything specific. You won't do very low-level stuff because the JVM is too heavy. Data science stuff, well Python has a better community. Web stuff - why not JavaScript? For almost everything, there's a language or ecosystem that seems like a better choice if you want to do this one thing.
  So where it really shines is huge monolithic projects that unify a lot of different domains.
  This is an extremely competent guide to the setup of clojure for windows users. The first 10 minutes were truly joyful; I can see the rest will also be "life changing", but it will take me a while longer. We are blessed to have Adaś thoughtful step-by-step guide, not to mention insightful asides (really useful to know stuff). Adaś says "In only 10 minutes you'll be spared weeks of frustration". So true, but the sheer joy, to have spent some time looking for a way in, to actually find it here!
  Thank you!
  This is indeed, quite a comprehensive guide, however, it has one premise that is not entirely accurate: "On the most fundamental level Clojure is just a Java program." This is untrue (to a point) Clojure is a JVM language,it compiles directly to byte code, it is not Java, although interop is entirely integrated. Java is an OOP language, Clojure is a functional Lisp, there are fundamental differences. Its true Clojure processes are built on the Java Memory Model ( the memory model is JVM based not language-centric), but in context Java and Clojure are different languages,that share the same underlying platform, but to say Clojure is a Java Program is not beneficial. Clojure can use Java Libs, but as semantics and approach it is does not share the paradigm.
  It's absolutely on a fundamental level a Java program, its low-level source code is perfectly readable Java code. All the compiler magic is fairly contained. When dealing with trickier issues you will absolutely be looking at the Clojure's Java source or the Java source of the many builtin Java libraries it uses for pretty much everything. Happens all the time.
  Never encountered an issue where you'd even think about bytecode generation or JVM internals. When I started with Clojure the only "hard" part was catching up on all the Java stuff since that was never my thing. Hence this guide tries to explain both at the same time.
  It very much depends on how you define the concept of program. To me A Clojure program is a program written in Clojure, a Java program is one written in Java. Whilst they share the underling JVM and some libraries they are not the same, and yes even when there is interoperable features.
  Kotlin and Groovy are also JVM languages, are they also Java programs?
  For me, a program is about the way you approach your need in a language, the core facets of the language, how its structured how its implemented. Its not merely about syntax.
  In this case, Clojure is a data driven, functionally opinionated way of approaching a solution on the JVM (it also has variants that run on other run times). It provides more facile less bloated ways of abstraction and immutability (though newer versions of Java have improved on this). These are fundamental facets of functional programming and give Clojure its identity, as a Clojure program.
  Certainly as mentioned it is possible to provide some of these abilities an a Java Program, but Java is not inherently functional, the predominant way Java is used is primarily different from the way Clojure is used, even if it is to achieve the same outcome.
  As a clearer analogy for example, the Norwegian and Swedish languages (and to an Extent Danish) share common lexical foundations, some common words, constructs, semantics and linguistic similarities, but Norwegian is not Swedish nor Danish, neither is Danish Norwegian or Swedish or Swedish Norwegian or Danish. Each has its own applications, uses, nuances and contexts, but each is not the other. One wouldn't initially choose to speak Danish in Norway to provide a solution to communicate.
  So for me, my decision on what language to use, depends on the individual strengths and characteristics as well as its issues and foibles, basically, how best it could provide a solution for the domain issue faced. This defines for me, what a program is, and if I apply such logic here, I would not consider Clojure a Java Program, nor Clojure a Java Program, as they both approach domain issues differently.
  This is not to disagree nor critisize your guide, certainly not, I enjoyed it, it was well written and researched,an excellent guide. As someone who works with both Java and Clojure (and admittedly, a big functional programming and Clojure fan), I personally took slight umbrage with your statement about Clojure being a Java program.
  Right, I'm absolutely with you as far as the concept of Clojure the language goes. A Clojure program is a Clojure program. While Clojure itself, the runtime and compiler, is a Java (and Clojure) program. And it's not just a technical detail, but something that is very apparent when working on any non-trivial project. RT.java is something you'll be seeing in your stacktraces a lot.
  Say, with ClojureScript, the situation is different. CLJS code AND its runtime gets compiled (by the JVM Clojure) into JavaScript code that abuses JS quite heavily and only has decent interop one-way. I don't think any parts of the CLJS runtime are implemented in JavaScript, it's all generated from Clojure I believe. If you have a problem with the CLJS implementation you will be looking at Clojure[Script] code. Also the Clojure compiler is present at runtime, while ClojureScript's is obviously not.
  No idea how Kotlin or Groovy are implemented.
  Updated the wording to "Clojure runtime and compiler", hopefully this eliminates any confusion
  Great write up, been getting into Clojure and windows at the same time, thus far I've been using WSL with VSCode and leiningen which has worked well for learning and smaller things. It seems like everyone is using EMACS to develop with and I'm wondering if there are any pitfalls to setting this up in windows as well? (The Emacs setup failed miserably on my mac with the latest Catalina which made me a little wary of trying on windows)
  This is a really great guide. I know java and the jvm fairly well and I know Lisp but never knew how the two came together to make clojure, as well as the tools that come with it.
  Thanks, glad you enjoyed!
  Wow this saved me days off installation. very good article.Thanks for sharing
  Isn't Clojure support for Windows still in alpha? If that's the case, would it be better to run on Linux through WSL?
  Some comments have been hidden by the post's author - find out moreCode of Conduct • Report abuse