1. 31
  1.  

    1. 2

      I can’t for the life of me seem to figure out what to do with a clojure program after I have written it. How do I take a .clj file and turn it into a .class?

      I don’t want to set up a leingen project or whatever, I don’t want to bundle it as a jar, all I want to do is write a .clj file in a text editor and turn it into a .class so I can run it like java ClassName

      1. 2

        Author of the article here. Clojure is more commonly loaded and compiled on the fly, so the easiest way to run a single .clj file is to just do clojure your_file.clj. This uses the official Clojure CLI. Compiling to class files (AOT compilation) is usually reserved for production deployments, where you build an (uber)jar.

        That said if you want to just compile a single namespace, you do that with Clojure’s compile function. It’ll look for the namespace on the classpath (defaults to src), and write the resulting classes to classes.

        ;; src/foo.clj
        (ns foo
          (:gen-class))
        
        (defn -main [& args]
          (apply println "GOT ARGS" args))
        

        Then from the shell:

        $ mkdir classes
        $ clojure -e "(compile 'foo)"
        

        You’ll need the Clojure and Spec jars to be able to run this:

        $ java -cp $HOME/.m2/repository/org/clojure/clojure/1.11.2/clojure-1.11.2.jar:$HOME/.m2/repository/org/clojure/spec.alpha/0.5.238/spec.alpha-0.5.238.jar:classes foo hello
        GOT ARGS hello
        
        1. 1

          Why a .class? I use this trick to make my .clj file executable:

          #!/bin/bash # -*- mode: Clojure; -*-
          #_(
          
             #_DEPS is same format as deps.edn. Multiline is okay.
             DEPS='
             {:deps {
                     clj-http/clj-http {:mvn/version "3.12.3"}
                     cheshire/cheshire {:mvn/version "5.11.0"}
                     }}
             '
          
             #_You can put other options here
             OPTS='
             -J-Xms4m -J-Xmx256m
             '
          #_Install Clojure if not present on the system (java is needed though)
          
          LOCAL_CLOJURE_DIR="$HOME/.local/share/clojure"
          CLOJURE="$LOCAL_CLOJURE_DIR/bin/clojure"
          if [[ ! -x "$LOCAL_CLOJURE_DIR/bin/clojure" ]]; then
            [[ ! -d "$LOCAL_CLOJURE_DIR" ]] && mkdir -p "$LOCAL_CLOJURE_DIR"
            pushd "$LOCAL_CLOJURE_DIR"
            #_Using Posix instructions https://clojure.org/guides/install_clojure#_posix_instructions
            curl -L -O https://github.com/clojure/brew-install/releases/latest/download/posix-install.sh
            chmod +x posix-install.sh
            ./posix-install.sh -p $PWD
            popd
          fi
          
          exec $CLOJURE $OPTS -Sdeps "$DEPS" -M "$0" "$@"
          )
          
          (ns my-program
            (:require [clj-http.client :as client]
                      [clojure.pprint :as pp]
                      [clojure.java.io :as io]
                      [clojure.java.shell :refer [sh]]))
          
          (defn -main [& args]
            (pp/pprint args))
          
          (apply -main *command-line-args*)
          

          https://gist.github.com/yogsototh/dc16e3213115c8305720f8c74d4a8297

          1. 1

            If you’re using deps.edn to build your clojure project you probably want tools.build to produce an uberjar. https://clojure.org/guides/tools_build#_compiled_uberjar_application_build

            1. 1

              Why do you want it to run like java ClassName in the first place? To me that seems a bit arbitrary, and really doesn’t work unless you add the Clojure runtime on the classpath.

              Though I assume you just want to run a .clj file as a command on the command line, and there Babaskha is the de-facto standard tool to that these days.

              1. 1

                Here’s a raw way with cli-tools:

                $ mkdir -p classes
                $ echo '(ns hello (:gen-class :main true)) (defn -main [] (println "Hello world!"))' > hello.clj
                $ clojure -Sdeps '{:paths ["."]}' -e "(compile 'hello)"
                WARNING: Implicit use of clojure.main with options is deprecated, use -M
                hello
                $ java -cp classes:$HOME/.m2/repository/org/clojure/clojure/1.12.0/clojure-1.12.0.jar:$HOME/.m2/repository/org/clojure/spec.alpha/0.5.238/spec.alpha-0.5.238.jar hello
                Hello world!
                

                Obviously, if you want an easier way of setting up the classpath, then the tooling is there for you. But if you don’t want to use it…

                1. 1
                  $ mkdir -p src/example
                  $ cat >src/example/core.clj <<EOF
                  (ns example.core
                    (:gen-class))
                  
                  (defn -main
                    [& args]
                    (println "foo"))
                  EOF
                  $ mkdir out
                  $ java -cp clojure.jar:src -Dclojure.compile.path=out clojure.lang.Compile example.core
                  Compiling example.core to out
                  $ java -cp clojure.jar:out example.core
                  foo
                  
                2. 2

                  Another aspect that helps here is that Clojure generally avoids polymorphism.

                  Maybe the author meant that it avoids inheritance?

                  Clojure’s polymorphism story is IMO one of the nicest features of the language. Protocols offer true composition over inheritance, whereas in most languages you still have to know which concrete class to instantiate ahead of time to take advantage of your own extension. But with protocols you can just say “abstraction X behaves this way when .someMethod gets called on it,” regardless of whether X was defined in your code of some third-party lib. I miss this in every other language.

                  And multimethods are just chef’s kiss