Trying to pass an argument to a Clojure program

Hi,

I’m just starting to learn Clojure. Most of my (limited) programming experience is in shell scripting. In trying to write something that will calculate factor pairs, I came across this:

https://gist.github.com/ecmendenhall/5970568
http://ecmendenhall.github.io/blog/

To incorporate that code, I did lein new app factorpairs and edited factorpairs/src/factorpairs/core.clj to look like so:

(ns factorpairs.core
  (:gen-class)
  (:use [clojure.core.logic :refer :all])
  (:use [clojure.core.logic.fd :refer [in interval eq]]))

(defn factor-pairs [number]
  (run* [pair]
		(fresh [factor1 factor2]
			   (in factor1 factor2 (interval 2 number))
			   (eq (= number (* factor1 factor2)))
			   (== pair [factor1 factor2]))))

(defn -main [x]
  (println (factor-pairs x)))

Also edited factorpairs/project.clj to be:

(defproject factorpairs "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
			:url "https://www.eclipse.org/legal/epl-2.0/"}
  :dependencies [[org.clojure/clojure "1.11.1"]
				 [org.clojure/core.logic "1.0.1"]]
  :repl-options {:init-ns factorpairs.core}
  :main factorpairs.core)

But when I try get the factors for a number, say 40:

lein run 40

I see the following:

WARNING: == already refers to: #'clojure.core/== in namespace: factorpairs.core, being replaced by: #'clojure.core.logic/== Execution error (ClassCastException) at clojure.core.logic.fd/interval (fd.clj:372).class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')

If I edit -main to place the number I wish to factor directly like so:

(defn -main []
  (println (factor-pairs 40)))

and do

lein run

It kinda works:

WARNING: == already refers to: #'clojure.core/== in namespace: factorpairs.core, being replaced by: #'clojure.core.logic/==
([2 20] [4 10] [5 8] [8 5] [10 4] [20 2])

But I’d like to be able to pass the number as an argument on the command line.

And I’m not sure what’s going on with that “WARNING: == already refers …” message. Did some searching about but could not sort out how to resolve that.

Thanks for any help / suggestions,

John

When you use the stuff from core.logic (via the :use clause in the ns macro), it effectively rebinds the current vars in the namespace, one of which is == which exists in clojure.core . By default (there are options to specify when/if/how this happens), the clojure ns namespace macro will refer-clojure which binds all the vars in the clojure.core namespace in the current namespace (not unlike what is happening in the :use option to the ns macro). So now in the context of your factopairs.core ns, == will evaluated to clojure.core.logic/== which is what the warning is telling you. You can fix this by explicitly requiring (another clause, e.g. :require) the namespaces and providing an alias (pretty common idiom these days), or providing some exclusions to :use. use and its appearance as a clause in the ns macro were popular in the early days (a lot of examples show them), but they obfuscate the origin of symbols and pull in every public var (also yielding issues like these symbol collisions). Most folks prefer explicit require and having some tracing of the var in usage.

The other issue (the actual error) is

Execution error (ClassCastException) at clojure.core.logic.fd/interval (fd.clj:372).class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')

due to the command line arg to -main is a string that you must parse manually. interval expects a number and it got a string. So one option is to (clojure.edn/read-string x), or use the more current (parse-long x) from clojure.core (circa 1.11).

(ns factorpairs.core
  (:gen-class) ;;this is only necessary for AOT compilation, and -main classes
  (:require [clojure.core.logic :as log])
  (:require [clojure.core.logic.fd :as fd]))

(defn factor-pairs [number]
  (log/run* [pair]
		(log/fresh [factor1 factor2]
			(fd/in factor1 factor2 (fd/interval 2 number))
			(fd/eq (= number (* factor1 factor2)))
			(log/== pair [factor1 factor2]))))

(defn -main [x]
  (println (factor-pairs (parse-long x))))

or

(ns factorpairs.core
  (:gen-class)
  (:refer-clojure :exclude [==]) ;;must use fully qualified clojure.core/== explicitly in your code.
  (:use [clojure.core.logic :refer :all])
  (:use [clojure.core.logic.fd :refer [in interval eq]]))

You could alternately ignore the warning.

The clojure cli is an alternate way to provide scripting. Given a root directory with
./src/factorpairs/core.clj
./deps.edn

where deps.edn:

{:deps {org.clojure/core.logic {:mvn/version "1.0.1"}}}

and factorpairs is one of the working solutions from above, then there are a few ways to invoke
-main as a script (-main isn’t really necessary either) akin to lein run:

PS C:\Users\joinr> clj -M -m factorpairs.core 42
([2 21] [3 14] [6 7] [7 6] [14 3] [21 2])
1 Like

Thanks for the great explanation – much appreciated!

For a simplified shell command I did lein uberjar and made a simple shell script factorpairs to call the resulting jar:

#!/bin/sh
java -jar $HOME/bin/factorpairs/target/factorpairs-0.1.0-SNAPSHOT-standalone.jar $1

Seems the jar also has a faster startup than when using lein or clj.

I tried re-writing the clojure into a babashka script, but got the error Could not resolve symbol: definterface, then saw in the babashka readme “No deftype, definterface and unboxed math”. So I gather it’s not possible to use core.logic with babashka.

BTW, my interest in calculating factor pairs came from trying out bitmap font sizes on a display n pixels tall. So for a 2560 x 1600 display, factorpairs 1600 provides an idea of what range of character cell heights fit evenly onto the screen. Might be some existing tools for this, but thought I’d try writing some Clojure as a learning exercise… and your reply was very helpful in this regard, thanks again.

Seems the jar also has a faster startup than when using lein or clj.

Some things to be aware of with lein:

It will by default launch 2 java processes, one for the project/classpath stuff and one for the actual clojure repl (unless you use lein trampoline I believe). The second process has some startup cost. It may (I don’t know if they changed this) inject some options to the jvm to have faster startup by restricting the JIT (as noted here), another source of runtime and/or startup time differences. Also, enabling the parallellgc (as opposed to g1gc which I think is the default) probably yields better runtime performance for clojure workloads.

clj won’t launch a second process, but it does need to resolve deps and cache them the first time. It should theoretically be faster than lein run in practice due to less overhead, and it doesn’t inject jvm options.

The uberjar should be similar to the clj performance, at least in startup cost (in theory). I didn’t see any AOT (ahead of time compilation) directions in your project.clj; if you set that up, it might be a bit faster to load the uberjar variant since the bytecode is already emitted.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.