Search code examples
clojure

What does the - (minus) symbol in front of function name within Clojure mean?


I can't get my head around the following. When defining the main function in Clojure (based on code generated by Leinigen), there's an - symbol in front of the function name main.

I went to the original documentation on clojure.com and found defn and defn- among other things, see https://clojuredocs.org/search?q=defn. I also searched on Google and found a source that said that the - in front of main indicated that the function was static (http://ben.vandgrift.com/2013/03/13/clojure-hello-world.html).

Does the - truely mean that the function is static? I couldn't find any other sources that confirmed that. Also I can use both (main) and (-main) when calling the main method without any problem.

Given the following code...

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (println "Hello, World!"))

(defn main
  "I don't do a whole lot ... yet."
  [& args]
  (println "Hello, World!"))

I get the following output...

(main)
Hello, World!
=> nil
(-main)
Hello, World!
=> nil
Loading src/clojure_example/core.clj... done
(main)
Hello, World!
=> nil
(-main)
Hello, World!
=> nil

I noticed no difference. Output is the same for both functions. Any help is appreciated!


Solution

  • First, an aside.

    Regarding the macros defn vs defn-, the 2nd form is just a shorthand for "private" functions. The long form looks like:

    (defn ^:private foo [args] ...)
    

    However, this is just a hint to the user that one shouldn't use these functions. It is easy for testing, etc to work around this weak "private" restriction. Due to the hassle I never use so-called "private" functions (I do sometimes use metadata ^:no-doc and names like foo-impl to indicate a fn is not a part of the public-facing API and should be ignored by library users).


    The "main" function in a Clojure Program

    In Java, a program is always started by calling the "main" function in a selected class

    class Foo
      public static void main( String[] args ) {
        ...
      }
    }
    

    and then

    > javac Foo.java   ; compile class Foo
    > java Foo         ; run at entrypoint Foo.main()
    

    Clojure chooses to name the initial function -main. The hyphen in the function name -main is not really special, except it makes the name unusual so it is less likely to conflict with any other function in your codebase. You can see this in the definition of the function clojure.main/main-opt.

    You can see part of the origin of the hyphen convention in the docs for gen-class (scroll down to see the part about :prefix). Note that using the hyphen is changeable if using gen-class for java interop.

    Using the Clojure Deps & CLI tools, the name -main is assumed as the starting point of the program.


    If you are using Leiningen, it is more flexible and allows one to override the -main entrypoint of a program.

    In Leiningen projects, an entry like the following indicates where to start when you type lein run:

    ; assumes a `-main` function exists in the namespace `demo.core` 
    :main ^:skip-aot demo.core    
    

    so in a program like this:

    (ns demo.core )
    
    (defn foo [& args]
      (newline)
      (println "*** Running in foo program ***")
      (newline))
    
    (defn -main [& args]
      (newline)
      (println "*** Running in main program ***")
      (newline))
    

    we get the normal behavior:

    ~/expr/demo > lein run
    
    *** Running in main program ***
    

    However, we could invoke the program another way:

    > lein run -m demo.core/foo
    
    *** Running in foo program ***
    

    to make the foo function the "entry point". We could also change the :main setting like this:

    :main ^:skip-aot demo.core/foo
    

    and get behavior:

    ~/expr/demo > lein run
    
    *** Running in foo program ***
    

    So, having the initial function of a Clojure program named -main is the default, and is required for most tools. You can override the default if using Leiningen, although this is probably only useful in testing & development.

    Please keep in mind that each namespace can have its own -main function, so you can easily change the initial function simply by changing the initial namespace that is invoked.

    And finally, the hyphen in -main us unrelated to the hyphen used for pseudo-private functions defined via defn-.