Fork me on GitHub
#beginners
<
2022-03-26
>
Apple01:03:00

Hi how do you pack direct and indirect lib dependencies into one .jar file? And with that .jar file can you run 'clj -J-cp"my.jar" -m xyz.core' to start a clj program?

Drew Verlee02:03:51

What is a indirect lib dependency?

ahungry02:03:08

probably means a transitive dependency (dependency of a dependency)

Drew Verlee02:03:06

There are multiple build tools in the clj ecosystem that will take a collection of deps and help you create a jar. The clojure deps tool chain is what i would start with.

Apple13:03:17

On the tools_build page if the jar build is for my source code and uberjar build is for my source code plus libs, is there a way to just pack the libs?

seancorfield13:03:47

The build is a program so you can do whatever you want.

Apple05:03:43

-rw-r--r--  1 x x   6195133 Mar 27 01:13 x-0.1.17-clj.jar
-rw-r--r--  1 x x  33289626 Mar 27 01:08 x-0.1.17-lib.jar
-rw-r--r--  1 x x  39477596 Mar 26 18:49 x-0.1.17-standalone.jar
I normally use java -jar x-0.1.17-standalone.jar to start the app. Now that I have the my clj files separated from the packed libs, and try following I just get Exception in thread "main" java.lang.NoClassDefFoundError: clojure/lang/Var. Any hint?
java -classpath x-0.1.17-lib.jar                  -jar x-0.1.17-clj.jar
java -classpath x-0.1.17-clj.jar:x-0.1.17-lib.jar -jar x-0.1.17-clj.jar
java -cp        x-0.1.17-lib.jar                  -jar x-0.1.17-clj.jar
java -cp        x-0.1.17-clj.jar:x-0.1.17-lib.jar -jar x-0.1.17-clj.jar

Apple05:03:46

java -classpath ./x-0.1.17-lib.jar:./x-0.1.17-clj.jar clojure.main -m x.core this works

Apple05:03:30

java -cp x-0.1.17-clj.jar:x-0.1.17-lib.jar x.core also works

Apple05:03:12

-jar jarfile Executes a program encapsulated in a JAR file. The jarfile argument is the name of a JAR file with a manifest that contains a line in the form Main-Class:classname that defines the class with the public static void main(String[] args) method that serves as your application's starting point. When you use -jar, the specified JAR file is the source of all user classes, and other class path settings are ignored. If you're using JAR files, then see https://docs.oracle.com/en/java/javase/18/docs/specs/man/jar.html.

Apple05:03:42

CLASSPATH=x-0.1.17-clj.jar:x-0.1.17-lib.jar java x.core also works

Apple05:03:01

"-jar" also ignore env

Apple06:03:10

https://docs.oracle.com/en/java/javase/18/docs/specs/jar/jar.html#class-path-attribute after adding this line Class-Path: x-0.1.17-lib.jar to manifest and updating x-0.1.17-clj.jar i'm finally able to just do java -jar x-0.1.17-clj.jar

John Bradens04:03:47

Quick question. I'm playing around with an app that is all cljs files, no clj. It doesn't have a project.clj file. I want to deploy it to Heroku to test it out there. Heroku says it needs a project.clj file to work with Clojure. So I need to create a project.clj file, but what do I put in it? Can it just be blank? Does the contents depend on my project? all the dependencies are in shadow-cljs.edn & package.json

practicalli-johnny18:03:11

Unless you need a persistent store, then ClojureScript projects can be deployed on GitHub or GitHub pages. For Heroku, a simple approach is to take the generated project files (any html/CSS /js files) and add them to a simple node.js express / python / any language your prefer project that provides a web server to serve static files. Deploy that project to Heroku Or add a sever-side web server to the ClojureScript project. You may still need to build the ClojureScript files before depolying to Heroku (or use a multi-buildpack approach)

dpsutton04:03:40

Bit of background. clojurescript will turn into javascript. and javascript needs to be served by a server. So most likely this is a frontend. Shadow-cljs is a lovely build tool that helps you use clojurescript. If you have node installed, try running npm install and then shadow watch :app from a terminal. You'll likely get a message that it is serving the javascript (the compiled clojurescript) on a port and you can visit that port in a browser

dpsutton04:03:06

if you share a link to the repo i can help you more concretely

John Bradens05:03:35

I'm trying to understand how the database works. People can post articles (it's a clone of Medium). But in localhost, I can see my messages, but in incognito, the messages don't show up. It seems like the messages are only stored locally. I thought if I put it on heroku I could better understand how the data is being stored instead of trying to keep switching browsers and incognito mode. But that might be totally misguided. I realize now that I should probably dig into the code more to see how the data is being stored.

John Bradens05:03:24

This leads me to a follow up question, which is how could I connect this repo to something like postgres? Or how else could I get the data to be stored correctly?

seancorfield05:03:37

Sounds likely that the data is being stored via the browser, in local data.

seancorfield05:03:20

If you want to store it in a regular database, you're going to need to write a Clojure backend that serves an API over HTTP -- and that part you could run on Heroku.

seancorfield05:03:44

And then modify the front end to call the back end via that HTTP API.

John Bradens05:03:36

Ok, I see. Thanks. To set up a Clojure backend as a beginner, what would be the best way to do that? Should I use a framework?

seancorfield05:03:45

There are no frameworks.

seancorfield05:03:01

A simple HTTP API could be built with compojure and ring.

seancorfield05:03:40

(it could be built with just ring -- but compojure makes it a bit easier to handle the mapping of routes to handlers)

John Bradens05:03:53

Ok so should I look for a tutorial that uses compojure & ring? Or look at the documentation?

seancorfield05:03:26

Plus whatever database library you want to use (e.g., next.jdbc + the PostgreSQL driver). Plus you'd need an actual database server in the mix too.

John Bradens05:03:34

So far I've only done tutorials straight through & have not added a clojure back end before so this will be new to me. When I asked about frameworks, I meant something like luminus where you can add postgres & http-kit when you set it up.

seancorfield05:03:23

Luminus is fine once you understand all the moving parts. Otherwise it is opaque and if anything goes wrong you'll be stuck trying to figure out all those parts.

John Bradens05:03:29

Do you think using something like luminus might make things easier & then add front end features from the current repo to it? Or should I just add the back end stuff from scratch to the current repo?

John Bradens05:03:42

True, I've been very stuck with trying to figure out the parts.

John Bradens05:03:46

Thanks for the link!

seancorfield05:03:52

(I actively recommend beginners do *not* try to use Luminus)

dpsutton05:03:06

for sure one piece at a time

John Bradens05:03:52

Ok that's really good to know

John Bradens05:03:58

Thanks for the help!

seancorfield05:03:59

At some point I'll make a variant of usermanager that exposes a REST API and has a re-frame front end. In my copious free time.

😆 1
dpsutton05:03:28

"how do i read/write to a database". read up on next.jdbc. with postgres createdb foo and then start making tables, reading/writing from Clojure. "how do i respond to a request". look at the docs for ring. set up a little handler on a port and respond back with a string hello world. then figure out how to respond with some json

dpsutton05:03:09

"how do i organize all of this" a very distinct question from the ones above. when you know how to use a database from clojure and how to respond to web requests, linking up the pieces the way sean shows there is quite nice

John Bradens05:03:40

Ok great. Thanks again for the help. I feel like I've been doing Luminus tutorials & not understanding the pieces, and then trying to look at the individual pieces & not understanding how they can all fit together. I think all this info & the example will help bridge that gap for me

seancorfield05:03:54

I also think Luminus makes some questionable choices about libraries (but that's very subjective). There's also a new "framework" by the same author that is intended to be a replacement for it, called Kit (I think).

seancorfield05:03:37

Yeah, https://kit-clj.github.io/ -- and it improves on some of those choices, as well as being deps.edn and build.clj based.

John Bradens05:03:15

Ok do you suggest I look into Kit? Or maybe wait until I can build something from scratch first?

seancorfield05:03:54

Build something from scratch.

seancorfield05:03:18

(sean)-(jobs:0)-(~/clojure/fresh/guestbook)
(! 797)-> clojure -X:deps list|wc
      86     267    3674
That's the "barebones" Kit guestbook demo project.

John Bradens05:03:36

Ok. I'm really impatient and I've been resisting this advice but I'm going to try it this weekend 😅

seancorfield05:03:46

Even discounting the various org.clojure and metosin libraries, that "barebones" project still has nearly 60 dependencies:

(sean)-(jobs:0)-(~/clojure/fresh/guestbook)
(! 798)-> clojure -X:deps list|fgrep -v org.clojure|fgrep -v metosin|wc
      57     180    2515

John Bradens05:03:34

What libraries do you suggest I use for building an app from scratch like a Medium clone? I figure I'll start with the ones you have in usermanager, and then you mentioned re-frame. Is there any other libraries I should look into?

John Bradens05:03:30

Wow! That is a lot of dependencies in the framework

seancorfield05:03:10

I know almost nothing about FE dev but for the BE, what's in usermanager is pretty much all you need: component (for resource lifecycle stuff), ring (basic HTTP), compojure (for route mapping), next.jdbc (+ your DB driver), selmer (for HTML templates). The ring-defaults lib just makes it easier to get a Ring-based app up and running with sane defaults for middleware (form parameters, JSON, etc).

seancorfield05:03:17

Linked from that example repo is a version of usermanager that is based on integrant/reitit (instead of component/compojure) so look at that as well to decide whether you prefer those libs.

seancorfield05:03:49

(and a lot of this is subjective/preference -- but as a beginner it's hard to have a preference)

John Bradens05:03:32

Ok good to know! I'll start with the backend then & then I'll come back to slack and ask more detailed questions about the front end when I'm ready

seancorfield05:03:01

When I build the FE/BE version of usermanager, I'll use re-frame and figwheel-main as a base for the FE. No idea what else I'll end up with (re-frame is a wrapper/architecture built on top of reagent).

John Bradens05:03:40

Ok cool! I'll start with those then. I've done a few tutorials with re-frame & figwheel, still trying to understand them

John Bradens07:03:17

@U04V70XH6 Why don't you have a project.clj file in usermanager? What's the difference between projects that have one or not? I'm trying to understand when it's used & when it isn't.

seancorfield12:03:05

I use the Clojure CLI and deps.edn instead of Leiningen (`project.clj`). I switched from Leiningen to Boot back in 2015 and then to the CLI in 2018 - so I haven't used lein for nearly seven years (unless I'm trying to help a beginner who is having problems with Leiningen).

👍 1
John Bradens17:03:28

Ok thanks, that's good to know!

Baye11:03:05

Hi, for those who have written monolithic or SPA web apps in popular frameworks such as Ruby on rails, django, or react +javascript backend: how do you compare your productivity in those compare to Clojure web app (whether monolithic or SPA)...how doable is writing a small web /medium size web app for a solo developer?

Drew Verlee11:03:13

Comparing ror to clojure isn't easy, better to compare ror to a full stack solution in clojure like fulcro.

Baye12:03:45

I see. But the gist of the question is how does Clojure stack up in terms of productivity considering whatever framework you are thinking of.

Drew Verlee13:03:56

All things being equal, it would be comparable.

Drew Verlee13:03:36

It wouldn't be the decieding factor of success for the project.

👍 1
aratare13:03:25

I'm not sure if this answers your question, but for me, who came from Java land, the biggest advantage of using Clojure is having a REPL so I can interactively write code and tests with the smallest feedback loop possible. So in terms of productivity, I can actually spend time more efficiently and effectively using Clojure than any other language I've used so far. In terms of frameworks, they're pretty much similar regardless of whatever language you're using.

👍 1
aratare13:03:28

Another point that I've started to really appreciate is how Clojure code has longer lifespan compared to others. So if I find a Clojure library written by someone 5 or 10 years ago, I can still plug it in and have it working perfectly. I certainly can't say the same for Java. It does not directly influence productivity, but it still means you don't have to spend time trying to squeeze the library into your project.

👍 1
practicalli-johnny18:03:03

I found it very easy to write a SPA using ClojureScript, reagent and figwheel-main (starting with https://github.com/bhauman/figwheel-main-template ) Adding a CSS framework like http://Bulma.io or tailwind I find other languages and frameworks more complex to work with. For more complex state or interaction, then re-frame is very useful. If lots of JavaScript packages are essential, then a shadow-cljs approach may be more useful. https://www.jacekschae.com/view/courses/learn-reagent-free provides a good intro

Baye03:03:49

Thanks, very informative. Will check out your suggestions

Richie12:03:04

I think defrecord gives me a java class with some fields. I was surprised that I can assoc values to a record and get the record back. What enables this? What's happening? I haven't found docs on it.

(defrecord Person [first last])

(assoc (map->Person {:first "john"}) :what "hi")
;; #example.Person{:first "john", :last nil, :what "hi"}
(type (assoc (map->Person {:first "john"}) :what "hi"))
;; example.Person

Richie12:03:30

Maybe I just don't know java. I'm assuming I can't add new fields at runtime. Maybe that's wrong.

Richie12:03:46

I was thinking of it as like a c struct.

Richie12:03:40

https://puredanger.github.io/tech.puredanger.com/2010/11/23/implementing-java-interfaces-with-clojure-records/ "A function like assoc will create a new record instance with all of the old fields values plus any assoc’ed key/value pairs over the top (no structural sharing here like you get with maps)."

delaguardo12:03:07

https://clojure.org/reference/datatypes defrecord provides a complete implementation of a persistent map, including: value-based equality and hashCode metadata support associative support keyword accessors for fields extensible fields (you can assoc keys not supplied with the defrecord definition) etc

Richie12:03:19

Oh, thanks!

Drew Verlee13:03:12

It's worth mentioning, records are useful for performance critical paths. Otherwise hashmaps serve the same purpose and are easier to reach for.

1
seancorfield16:03:15

While you can assoc arbitrary fields into a record and still get that record type back, you need to be careful about dissoc'ing fields: if you dissoc a core (declared) field then you get back a hash map and you lose the record type -- which means you'll see (assoc my-record :some-field nil) to "remove" the field's value, which would otherwise be non-idiomatic in Clojure (keys in hash maps should generally either have "some" value -- non-`nil` -- or be omitted).

Richie16:03:25

Thanks! Yea, I wasn't reading carefully enough.

popeye15:03:40

i wrote simple function below in intellj, As per the definition of delay it should cache the value, But in this case it is not caching value, Am I doing it anything wrong ? Does this function evaluate everytime whenever compile again?

(defn practise-delay []
  (println "------1---")
  (let [k (delay (println "----2----")
                 (Thread/sleep 5000)
                 (println "---3--"))
        ]
    @k))

(println (practise-delay))

ghadi15:03:32

every time the function is called, it creates a fresh delay

popeye16:03:56

then how to use it? without executing body again?

ghadi16:03:54

one way is to make the delay outside the function

dominikk18:03:24

Hello! I need a hint regarding reagent, initial-values and state. I have a select item which gets populated from a database with users (using a for loop generating the hiccup). After initialization it shows the first user which is standard behavior, however this user is not represented in the r/atom as the selected user. This happens via :on-change once a user is actively selected. Can I call the :on-change function with the initially displayed value? Maybe there is a completely different approach to this kind of problem?

Dustin Paluch18:03:14

Consider asking in #reagent

E. Kevin Hall M.D.19:03:12

Can I ask a meta question? Is the fact that individual functional statements in a clojure namespace lay outside the scope of the namespace declaration an affordance? E.g. is this just an affordance for readability?

(ns my-namespace
  (:require ...)
  (defn foo [] (println "foo")))

andy.fingerhut19:03:39

I may be missing nuances of the question, but Clojure running on the JVM was designed to be a very dynamic language, which explicitly allows modifying the definitions of Vars in a namespace, adding new Vars to a namespace, etc. at run-time, at least during interactive development on a running JVM.

💯 1
Carlo19:03:54

The semantic of a namespace is a mutable one: it's a mutable container for vars. So calling ns is just to signal "I'm operating in that namespace now" more than "This is what this namespace is". This is done for the reason @U0CMVHBL2 mentioned above ^

adi19:03:57

Try (ns-publics *ns*) in whatever namespace you have now. Mine says this:

user=> (ns-publics *ns*)
{clojuredocs #'user/clojuredocs, help #'user/help, find-name #'user/find-name, user.proxy$java.lang.Object$SignalHandler$d8c00ec7 #'user/user.proxy$java.lang.Object$SignalHandler$d8c00ec7, cdoc #'user/cdoc, apropos-better #'user/apropos-better}

🙌 1
adi19:03:45

*ns* is a dynamic var that is automatically bound to the name of the current namespace.

user=> *ns*
#namespace[user]

adi19:03:05

We can fully introspect and modify namespaces. For example, I can remove the definition of help like this, from my live runtime: (ns-unmap *ns* 'help). The result of ns-publics will reflect this accordingly.

adi19:03:55

This is one facet of what it means to be "dynamic". Which brings us back to what Andy and Carlo said above.

Carlo19:03:15

Ok, but as I was trying to illustrate the operational viewpoint, I wrote at the repl:

Clojure 1.11.0
user=> (defn foo []
         (ns bar)
         (def baz 1)
         (ns user))
#'user/foo
user=> baz
#object[clojure.lang.Var$Unbound 0x518cf84a "Unbound: #'user/baz"]
user=> (foo)
nil
user=> baz
1
why is baz visible in the user namespace? Shouldn't it be visible in bar ?

adi19:03:14

Uneducated guess... It may have something to do with macroexpansion, special handling of ns forms or something. I'm eyeballing the results of macroexpansion...

(clojure.pprint/pprint (clojure.walk/macroexpand-all '(defn foo [] (ns bar) (def baz 1) (ns user)))) 

adi19:03:19

I can see push/pop to/from thread bindings something something, classloading etc..., which is lazy in the sense classloading order is not deterministic? I don't have enough JVM-fu to parse this. Going off to Ducksearch...

E. Kevin Hall M.D.19:03:02

Thanks everyone! I don't know if I worded it totally correctly. Just like lexical scope of a let binding (and what needs to be inside it's parentheses) seems at first pass a namespace's functions should also be inside the parentheses of the name space. But I think some people addressed that above and I also imagine I'm not fully grokking some of those answers. Thanks!

adi22:03:07

@U032WN2TKTR Your intuition is generally fine, viz. expressions ought to close over local scope. The difference is compile-time scope v/s run-time scope. ns is a macro, and it only does things at compile time. I will ham-hand explanations beyond this, so I'll suggest try this... Stare at the macroexpanded code for a while:

user=> (clojure.pprint/pprint (clojure.walk/macroexpand-all '(ns foo)))
Along with this:
user=> (clojure.repl/doc ns)
Cross-referenced with these: https://clojure.org/reference/macros https://clojure.org/reference/namespaces https://clojuredocs.org/quickref#namespaces

❤️ 1
ahungry01:03:09

I think design wise, it's an affordance as you originally surmise - to me, it's always looked as if it meant to follow the same structure/layout as import/namespace statements in other languages - a line at the top of a file, where everything in the file itself shares the space - it'd be really pointless to nest an entire .clj file's source code in a ns form, although more scheme-y in some ways I guess (like how SICP mentions bundling function definitions within a def to have an OOP/ns-like approach)

E. Kevin Hall M.D.19:03:42

☝️Isn't that technically more correct? (albeit less easy to read?)

John Bradens19:03:02

I have a question about build tools & project.clj files. Heroku docs say clojure deployment to heroku only works if you have a project.clj file. But I learned that project.clj files are only generated by leiningen and not other build tools like boot & cli. So if you don't use lein & want to deploy your project to heroku, what would you do? Do you create a project.clj file? What would it look like compared to deps.edn? Or would you start your project over using lein?

Carlo19:03:37

There are two workarounds for this, one is using a buildpack (I hadn't much success with this one myself) https://elements.heroku.com/buildpacks/raymcdermott/heroku-deps-build-pack The second one would be creating an empty project.clj file to convince Heroku that it's a clojure project, but then create an uberjar instead and upload it via a script in ./bin/build, which is the approach suggested here https://www.gertgoet.com/2019/02/06/deploying-a-tools-deps-clojure-project-to-heroku.html Unfortunately the blog post is a bit outdated in a couple of senses, but I can try to guide you through how it would be done with tools.deps nowadays.

Carlo19:03:18

My deps.edn would contain something like:

{:deps {...}

 :aliases
 {:build {:extra-deps {io.github.clojure/tools.build {:git/tag "v0.8.1" :git/sha "7d40500"}}
          :ns-default build}}

Carlo19:03:08

the :build alias is intended to import the tools.build toolchain, and evaluate the code in build. Here's ./build :

Carlo19:03:44

(ns build
  (:require [clojure.tools.build.api :as b]))

(def lib 'cheffy)
(def version "0.1")
(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def uber-file (format "target/%s-%s-standalone.jar" (name lib) version))

(comment
  [lib version class-dir basis uber-file])

(defn clean [_]
  (b/delete {:path "target"}))

(defn uber [_]
  (clean nil)
  (b/copy-dir {:src-dirs ["src" "resources"]
               :target-dir class-dir})
  (b/compile-clj {:basis basis
                  :src-dirs ["src"]
                  :class-dir class-dir})
  (b/uber {:class-dir class-dir
           :uber-file uber-file
           :basis basis
           :main 'cheffy.server}))

(comment
  (clean nil)
  (uber nil))
we define two actions (`uber` for creating an uberjar, and clean for cleaning the artifacts)

Carlo19:03:01

As you can see the main artifact is defined in:

(def uber-file (format "target/%s-%s-standalone.jar" (name lib) version))
Then we have a script in ./bin/build to actually call the uber command:
#!/usr/bin/env bash
clojure -Srepro -T:build uber

Carlo19:03:46

and finally, in Procfile, we tell Heroku to run that program:

web: java -jar ./target/cheffy-0.1-standalone.jar

Carlo19:03:24

hope this was somehow useful 😄

John Bradens19:03:21

This is super useful! Thanks

John Bradens19:03:32

I'm not trying to deploy today, but I've only deployed using lein projects in the past, and now I'm thinking about starting a project using deps.edn instead, and so I wanted to have some understanding of how I'd deploy to heroku in the future. I took screenshots of all this for future use

👍 1
practicalli-johnny21:03:28

This is how I deploy Clojure CLI projects to Heroku (You can skip the CircleCI part) https://practical.li/clojure-web-services/projects/banking-on-clojure/deployment-via-ci.html

vlad_poh21:03:02

Can you build a native desktop app with just clojure and distribute without java?

practicalli-johnny21:03:34

Building a desktop app is possible with Java FX. The internet suggests that Java FX will compile to a native image with Graal, so wouldn't need Java (as long as any other libraries used also supported Graal native compilation)

phronmophobic21:03:33

The short answer is yes. The long answer depends on what you mean by “without java”, what platforms you're targeting, and what type of application you're building.

vlad_poh21:03:18

@U7RJTCH6J i just want to share simple apps with non tech savvy coworkers. I have a lot of babashka scripts i used to do simplify my work. Trying to put a very basic ui together for them to fill in a few fields, click a button and see the result, get a file or email.

vlad_poh21:03:38

@U05254DQM I thought JavaFX and swing and SWT were all deprecated. I looked at electron but couldn't set it up

practicalli-johnny21:03:54

JavaFX was extracted into its own libraries and is till very much part of the Java platform. There are many examples of apps in the Clojure community build wit JavaFX and a very nice wrapper library for JavaFX too. Swing and AWS are likely to disappear sometime after all the COBOL code in the world has bee removed 😂

phronmophobic22:03:39

I'm not aware of any options that are easier to get started than electron. You can try https://gluonhq.com/developers/samples/ although you'll have to wade through some java-isms. I think gluon requires a license (either paid or open source exception) for the desktop version.

Jon Olick21:03:36

can you do rand-nth on a lazy list? Does that even make sense? what if its infinite?

dpsutton22:03:17

(source rand-nth) shows this won't work for an infinite list. In order to choose a random element it needs to know the bounds of the indices.

John Bradens23:03:05

I followed one tutorial that builds an app with the command

yarn dev || npm run dev
but more commonly I see
npx shadow-cljs watch app
what's the difference between using these two commands?

olaf23:03:27

npm is the node package manager, shadow-cljs is a build tool in clojure for manage clojurescript projects. In the case those commands do two different things: • npm run dev execute the dev command from your package.json, depends how is defined; more here https://docs.npmjs.com/cli/v8/using-npm/scriptsnpx shadow-cljs watch app is executing the shadow-cljs package via node, running app build from your shadow-cljs.edn

John Bradens23:03:10

Thanks! That helps a lot