clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
phronmophobic 2021-02-09T00:25:03.375900Z

Is there a recommended tool for compiling *.java files in a deps.edn based project?

seancorfield 2021-02-09T00:49:38.376400Z

@smith.adriane I tend to just shell out and run javac 🙂

phronmophobic 2021-02-09T00:52:50.377800Z

I guess I haven't run javac in a really long time. I just need clj to generate a class path to compile? To package the jar, I guess there's an option to tell depstar where to find the *.class files?

alexmiller 2021-02-09T00:57:38.378100Z

clj -Spath will give you the cp

👍 1
jumar 2021-02-09T03:18:17.379700Z

I've found an interesting example of a macro-generating macro in Joy of Clojure, 2nd ed (p. 360). I'm not completely sure why unit-of-distance has to be a macro and not just a function. Is the primary reason here performance? https://github.com/jumarko/clojure-experiments/blob/master/src/clojure_experiments/books/joy_of_clojure/ch14_data_oriented_programming.clj#L397-L465

(defmacro defunits-of [name base-unit & conversions]
  (let [magnitude (gensym)
        unit (gensym)
        units-map (into `{~base-unit 1}
                        (map vec (partition 2 conversions)))]
    `(defmacro ~(symbol (str "unit-of-" name))
       [~magnitude ~unit]
       `(* ~~magnitude
           ~(case ~unit
              ~@(mapcat (fn [[u# & r#]]
                          `[~u# ~(relative-units units-map u#)])
                        units-map))))))

(defunits-of distance :m
  :km 1000
  :cm 1/100
  :mm [1/10 :cm]
  :ft 0.3048
  :mile [5280 :ft])
;; => macroexpands to:
#_(defmacro unit-of-distance [G__21971 G__21972]
  (seq
   (concat
    (list '*)
    (list G__21971)
    (list
     (case
         G__21972
       :mm
       1/1000
       :m
       1
       :cm
       1/100
       :ft
       0.3048
       :km
       1000
       :mile
       1609.344)))))

(unit-of-distance 1 :m)
;; => 1

;; look how simple the expansion is!
(macroexpand '(unit-of-distance 1 :cm))
;; => (clojure.core/* 1 1/100)

jumar 2021-02-09T03:19:36.380Z

Where can I find more examples of macro-generating macros and what other use cases are there apart from moving the computation to compile time to achieve faster execution?

phronmophobic 2021-02-09T03:25:22.381600Z

macros generating macros is sometime used in cljs to work around not being able to programmatically add definitions to namespaces

alexmiller 2021-02-09T03:27:53.382700Z

Spec 2 has some macro generating macros for creating custom spec ops (which are macros)

p-himik 2021-02-09T06:23:29.382900Z

Hmm. More articles should use phrases like "custom spec ops". :D

2021-02-09T20:54:30.391800Z

Are there any general rules relating to defrecords vs deftypes when the fields are mutable? I have a thing that is a grouping together of a bunch of promises representing potential future states. Should I expose the promises with a protocol and make the thing via a deftype? Or use a record and expose the promises via it's keys (and potentially also a protocol?). A third option is to use a map, but since I do need this to participate in other protocols, I'm thinking that might be ruled out first.

phronmophobic 2021-02-09T21:06:32.393100Z

defrecord will also implement the protocols that enable assoc .based off your description, an assoced value might break some invariances. it seems like deftype might be preferable so you can limit which protocols are implemented to the ones that make sense

2021-02-09T21:12:32.393300Z

that makes sense, thanks!

Dominic Pearson 2021-02-09T22:20:40.394900Z

I have another really odd bug where it works at the REPL but fails when compiled.

❯ java -jar target/uberjar/enki-master-SNAPSHOT-standalone.jar
Using database: /home/dsp/.enki.db
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "clojure.lang.IFn.invoke(Object)" because the return value of "clojure.lang.IFn.invoke(Object)" is null
        at enki.db$create_all_tables$fn__15909.invoke(db.clj:50)
        at clojure.core$run_BANG_$fn__8790.invoke(core.clj:7715)
        at clojure.core.protocols$iter_reduce.invokeStatic(protocols.clj:49)
        at clojure.core.protocols$fn__8140.invokeStatic(protocols.clj:75)
        at clojure.core.protocols$fn__8140.invoke(protocols.clj:75)
        at clojure.core.protocols$fn__8088$G__8083__8101.invoke(protocols.clj:13)
        at clojure.core$reduce.invokeStatic(core.clj:6828)
        at clojure.core$run_BANG_.invokeStatic(core.clj:7710)
        at clojure.core$run_BANG_.invoke(core.clj:7710)
        at enki.db$create_all_tables.invokeStatic(db.clj:50)
        at enki.db$create_all_tables.invoke(db.clj:46)
        at enki.db$delete_and_recreate_database.invokeStatic(db.clj:63)
        at enki.db$delete_and_recreate_database.invoke(db.clj:58)
        at enki.core$_main.invokeStatic(core.clj:47)
        at enki.core$_main.doInvoke(core.clj:43)
        at clojure.lang.RestFn.invoke(RestFn.java:397)
        at clojure.lang.AFn.applyToHelper(AFn.java:152)
        at clojure.lang.RestFn.applyTo(RestFn.java:132)
        at enki.core.main(Unknown Source)
❯ lein repl
OpenJDK 64-Bit Server VM warning: Options -Xverify:none and -noverify were deprecated in JDK 13 and will likely be removed in a future release.
nREPL server started on port 44969 on host 127.0.0.1 - <nrepl://127.0.0.1:44969>
REPL-y 0.4.4, nREPL 0.8.3
Clojure 1.10.1
OpenJDK 64-Bit Server VM 15+36-Ubuntu-1
    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

enki.core=&gt; (-main)
Using database: /home/dsp/.enki.db
true
enki.core=&gt; 
I am using run! as recommended previously to force side effects on a seq, but I guess I must be doing it wrong? funcall that blows up:
(defn create-all-tables
  "Create database tables. Expects each table listed in the `tables` set
  to have a corresponding create-`table`-table function defined."
  [x]
  (run! #((-&gt; (str "create-" % "-table") symbol resolve) x) tables))
I trimmed -main down to:
(defn -main
  [&amp; args]

  (println "Using database:" (:subname db))
  (delete-and-recreate-database))
For full ref:
(defn delete-and-recreate-database
  []
  (let [database-file (io/file (:subname db))]
    (when (.exists database-file)
      (.delete database-file)))
  (create-all-tables db)
  (create-all-indexes db)
  true)
I am sure I must be doing something stupid.

seancorfield 2021-02-09T22:27:15.395800Z

@dsp Are all of those create-*-table functions in the same ns as create-all-tables?

Dominic Pearson 2021-02-09T22:27:34.396100Z

Yes, but not in the same ns as core

Dominic Pearson 2021-02-09T22:27:53.396400Z

I have [enki.db :refer :all] at the top.

Dominic Pearson 2021-02-09T22:28:31.397Z

Prior to the create-all-tables defn, I do:

(def tables #{"nodekeys"
              "contactkeys"
              "mykeys"
              "incoming"
              "outgoing"
              "authtokens"
              "data"
              })

(def indexes #{"data"})

(def tables-loaded (into #{} (map #(let [p (str "enki/db/sql/" % ".sql")]
                                     (hugsql/def-db-fns p) %)
                                  tables)))

Dominic Pearson 2021-02-09T22:28:48.397800Z

I know, kinda esoteric. I want to programmatically define the hugsql functions just by crawling the dir.

Dominic Pearson 2021-02-09T22:29:13.398700Z

Ah, I think I found the culprit.

seancorfield 2021-02-09T22:29:13.398800Z

create-all-tables is in enki.db -- and so are all the create-*-table functions, yes?

Dominic Pearson 2021-02-09T22:29:18.399100Z

Map again. Doh.

Dominic Pearson 2021-02-09T22:29:34.399500Z

I probably need to force that one. I keep tripping up on these old bits of seq code around the place...

Dominic Pearson 2021-02-09T22:29:49.400Z

Likely the def-db-fns isn't being evaluated except at REPL.

Dominic Pearson 2021-02-09T22:30:01.400300Z

Thanks for pointing my head in the right direction.

Christian 2021-02-09T22:30:39.401200Z

So, apparently Clojure is not a "real lisp" because I can't metaprogram at run-time and the code is recompiled on the fly. Is this because of the jvm as a base? https://softwareengineering.stackexchange.com/questions/210274/does-lisp-still-have-any-special-feature-which-has-not-been-adopted-by-other-pro#comment414013_210276

Bees 2021-02-10T11:26:08.428200Z

I feel like they are leaning towards the idea of a LISP Machine when they talk about it that way. It's a technique I think which would be interesting to explore, but I'm sure there is plenty of feedback as for why this is a bad idea. Instead of .clj files on a filesystem, you'd essentially program into a REPL and use ns to basically note that you're writing to a different file. The file would be archived in the environment, either spooled to a disk or even a database. From the REPL you could "open" a namespace and it would provide the user with a source code file where you could add comments, examples, etc. As you use different dependencies a graph could be kept with respect to what require/use statements are necessary, and the actual mapping would be applied 👋automagically:wave:. The source would be kept under version control in the system, so you could recall earlier versions of a namespace as well. My deeper vision is that this could build upon what @sritchie09 is working on so that the published form can be literal programming documentation. Maybe instead of a LISP Machine, it would be a Clojure Machine?

🎉 1
Sam Ritchie 2021-02-10T23:19:41.468300Z

it does seem that the deps.edn machinery and namespaces give us the primitives to at least see what that would feel like

Bees 2021-02-11T01:22:32.468600Z

@sritchie09, The biggest obstacle I can see is with respect to classpath imports. I'd also like to talk with you some other time about the SCIM and literal programming that you're looking at.

Sam Ritchie 2021-02-11T16:23:21.480200Z

@slack_clojurians let’s do it! shoot me a DM and we can find some time

seancorfield 2021-02-09T22:31:10.401900Z

into with map should be eager -- but it is kind of dangerous to have a top-level def with side-effects since that will run when the ns is loaded which will also happen during compilation (likely when you build the uberjar).

Dominic Pearson 2021-02-09T22:31:36.402200Z

Yep, doing doall didn't solve it.

Dominic Pearson 2021-02-09T22:33:09.402600Z

The only effect should be function definition I think? Is that dangerous?

dpsutton 2021-02-09T22:33:48.403100Z

Clojure is what it is. There's no universal definition for what a lisp is and then people argue based on their own definitions. I've seen someone claim Clojure isn't a real lisp because symbols are globally interned. Some people claim that if you can't evaluate code from 1962 you aren't a real lisp.

phronmophobic 2021-02-09T22:34:50.403800Z

It seems like the assertion that clojure can't be meta-programmed at runtime is wrong. Am I missing something?

phronmophobic 2021-02-09T22:35:41.404Z

> you never stop the running program, the running program itself is a set of objects in memory that you manipulate the same way you manipulate any other object, while it is running. working on my program while it's running is a big reason I enjoy clojure

borkdude 2021-02-09T22:36:00.404200Z

I have no idea what they are talking about

😌 1
dpsutton 2021-02-09T22:37:40.404600Z

i've never seen one of these discussions benefit anyone in any way. arguing what is a lisp on the internet is just a dumpster fire

😆 1
seancorfield 2021-02-09T22:41:18.407800Z

This is very much one of those "how many angels...?" type questions (and belongs in #off-topic at best since it is not a technical question that can be answered as asked).

Dominic Pearson 2021-02-09T22:42:43.409500Z

What I am essentially trying to accomplish is to programmatically do what the hugsql docs give as an example:

;; The path is relative to the classpath (not proj dir!),
;; so "src" is not included in the path.
;; The same would apply if the sql was under "resources/..."
;; Also, notice the under_scored path compliant with
;; Clojure file paths for hyphenated namespaces
(hugsql/def-db-fns "princess_bride/db/sql/characters.sql")
but instead of using manually-inserted paths, have them constructed from a set, and each table SQL definition file has a create-X-table (where X is the name of the table) function, and then have a function that can re-initialise the table by iterating through the set and calling the related definition. That it works at the REPL, but not when compiled, is perplexing me, esp as you say into should be eager. I also think it previously worked... If there is nothing obvious, I'll try ripping everything out and trying to pinpoint where things went wrong. If I am doing something really wrong or dangerous though, it's a good learning experience.

borkdude 2021-02-09T22:46:03.410400Z

@dsp "at the REPL, but not when compiled": what exactly do you do outside the REPL to kick this off?

Christian 2021-02-09T22:46:42.410700Z

I had no idea it is an idealogical thing. I was surprised to read that clojure can't be changed at run-time because it has to be recompiled. At the moment I take the recompilation as an "inbetween" step to be back again and then it's run time again. when it's quick it doesn't make a difference to me.

Dominic Pearson 2021-02-09T22:47:33.411600Z

Literally just

❯ java -jar target/uberjar/enki-master-SNAPSHOT-standalone.jar
which I would expect to just enter at -main in enki.core (and confirmed by the println). (-main) works at REPL.

borkdude 2021-02-09T22:48:28.412200Z

Effects like this should probably kicked off from your -main and not from the top level of your namespace

1
Dominic Pearson 2021-02-09T22:49:26.413500Z

I had wanted to keep db funcs in the db namespace for cleanliness, but maybe there is a way for me to do definitions there from core.

Dominic Pearson 2021-02-09T22:52:14.415400Z

It'll be exposed via HTTP API, so has to be accessible through the regular execution chain.

dpsutton 2021-02-09T22:53:00.416200Z

main functions need to return an integer right?

borkdude 2021-02-09T22:53:09.416400Z

not really

borkdude 2021-02-09T22:53:27.416700Z

(System/exit n) is what is the return code

borkdude 2021-02-09T22:53:37.417Z

or an exception will also trigger a non-zero one

Dominic Pearson 2021-02-09T22:53:40.417100Z

Am just trying to figure out exactly what's going wrong and why. When expanding -main like so:

(defn -main
  [&amp; args]

  (println "Using database:" (:subname db))
  (let [database-file (<http://clojure.java.io/file|clojure.java.io/file> (:subname db))]
     (when (.exists database-file)
       (.delete database-file)))
  (println tables-loaded)
  (println 'enki.db/create-data-table)
  (create-all-tables db))
I can see the following:
❯ java -jar /home/dsp/Development/enki/target/uberjar/enki-master-SNAPSHOT-standalone.jar
Using database: /home/dsp/.enki.db
#{contactkeys authtokens incoming outgoing data nodekeys mykeys}
enki.db/create-data-table
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "clojure.lang.IFn.invoke(Object)" because the return value of "clojure.lang.IFn.invoke(Object)" is
 null
       at enki.db$create_all_tables$fn__15912.invoke(db.clj:50)
       at clojure.core$map$fn__5866.invoke(core.clj:2755)
       at clojure.lang.LazySeq.sval(LazySeq.java:42)
       at clojure.lang.LazySeq.seq(LazySeq.java:51)
       at clojure.lang.RT.seq(RT.java:535)
       at clojure.core$seq__5402.invokeStatic(core.clj:137)
       at clojure.core$dorun.invokeStatic(core.clj:3133)
       at clojure.core$doall.invokeStatic(core.clj:3148)
       at clojure.core$doall.invoke(core.clj:3148)
       at enki.db$create_all_tables.invokeStatic(db.clj:50)
       at enki.db$create_all_tables.invoke(db.clj:46)
       at enki.core$_main.invokeStatic(core.clj:52)
       at enki.core$_main.doInvoke(core.clj:43)
       at clojure.lang.RestFn.invoke(RestFn.java:397)
       at clojure.lang.AFn.applyToHelper(AFn.java:152)
       at clojure.lang.RestFn.applyTo(RestFn.java:132)
       at enki.core.main(Unknown Source)

Dominic Pearson 2021-02-09T22:54:11.417600Z

So it looks as though the functions are indeed being defined in the right namespace.

seancorfield 2021-02-09T22:55:39.418400Z

As others have noted, the question and most of the answers there are... "opinions"... and many of those folks choose their definitions in such as a way as to support the points they want to make so their definitions are also... "opinions"...

Dominic Pearson 2021-02-09T22:55:48.418700Z

Indeed, a direct call in -main to that create-data-table function that is defined programmatically works. So I suspect it's related to the symbol resolve?

seancorfield 2021-02-09T22:57:22.418800Z

(most "opinion-based" questions on StackOverflow get shut down pretty quickly by the moderators so I'm a bit surprised something like that was allowed to "live" in the first place... but that probably says more about the Stack Exchange moderators than it does about the merit of the question 🙂 )

seancorfield 2021-02-09T23:02:15.420400Z

That's why I asked which functions are in which namespaces... but you would likely be safer to specify the namespace explicit in the symbol before calling resolve.

Dominic Pearson 2021-02-09T23:02:22.420600Z

Yep, that is definitely the issue.

Dominic Pearson 2021-02-09T23:04:38.422200Z

Strange that it works at the REPL. In db namespace I did:

(defn show-all-resolved-symbols
  []
  (map #(-&gt; (str "create-" % "-table") symbol resolve) tables))
and in -main in core I call it like
(println (into #{} (show-all-resolved-symbols)))
At REPL it is:
enki.core&gt; (show-all-resolved-symbols)
(#'enki.db/create-contactkeys-table
 #'enki.db/create-authtokens-table
 #'enki.db/create-incoming-table
 #'enki.db/create-outgoing-table
 #'enki.db/create-data-table
 #'enki.db/create-nodekeys-table
 #'enki.db/create-mykeys-table)
but when compiled it is: #{nil} I don't know why, but I will try explicit namespace references, I imagine that will work

seancorfield 2021-02-09T23:16:45.423Z

dev=&gt; (doc resolve)
-------------------------
clojure.core/resolve
([sym] [env sym])
  same as (ns-resolve *ns* symbol) or (ns-resolve *ns* &amp;env symbol)
^ *ns* behaves differently in the REPL to in an uberjar is the TL;DR.

seancorfield 2021-02-09T23:18:34.423900Z

If you do (-&gt; (str "enki.db/create-" % "-table") symbol resolve) I think it'll solve your problem @dsp

borkdude 2021-02-09T23:23:22.424700Z

or resolve from the other ns using ns-resolve - should yield the same (`(ns-resolve 'enki.db %)`)

Dominic Pearson 2021-02-09T23:24:29.425100Z

(defmacro create-all-tables
  "Create database tables. Expects each table listed in the `tables` set
  to have a corresponding create-`table`-table function defined."
  [x]
  `(run! #((ns-resolve (find-ns 'enki.db) (-&gt; (str "create-" % "-table") symbol)) ~x) ~tables))

Dominic Pearson 2021-02-09T23:25:16.425900Z

This seems to work. Thanks so much! I hope one day I more fully understand the pitfalls of relying on REPL behaviour as being akin to the endproduct.

Dominic Pearson 2021-02-09T23:26:58.426600Z

Gone midnight, bedtime for me. But glad to squash this bug that's had me pulling at my hair for the last hour or two 🙂 Appreciate the help folks.

2021-02-09T23:58:42.426900Z

I didn't try to dig into that StackOverflow discussion in depth, but at least from a first reading of what they were claiming about Clojure, it appears some of the opinions expressed about Clojure were factually incorrect. You definitely can build a Clojure program from the ground up in a running JVM, function by function, without ever having to start the JVM over again -- exactly as many other Lisps allow you to do in a REPL.

2021-02-09T23:59:21.427100Z

All Lisps allow you to stop that running program and start a new one using the same source code (if you have saved that source code somewhere accessible to the next running process).