Fork me on GitHub
#beginners
<
2020-09-18
>
seancorfield00:09:13

@curiouslearn clojure -m entry.point assumes you have the source of project at hand (i.e., on your production server), and that the Clojure CLI is installed (on your production server), and that you are running it in the project directory. This is a reasonable approach for example if you have your code checked out from version control on the server and you deploy new versions by running git pull on the server (which some people do) and just just restarting or reloading your project. java -cp the.jar clojure.main -m entry.point assumes you have a JVM on the production server (which you must have for the Clojure CLI too) and that you have the.jar on the server -- but you don't need the project structure (source code) and you don't need the Clojure CLI installed.

seancorfield00:09:20

In the latter case, you could build the.jar somewhere else and deploy just that file to the server (by whatever method you choose).

seancorfield00:09:33

We have one project in production that is deployed as source (legacy ColdFusion code that loads Clojure code at startup -- so we have Clojure CLI installed, plus all our Clojure source code, plus all our CFML source code). All our other projects are deployed just as AOT'd JAR files, and even tho' they all have a primary entry point, several of them have multiple -main functions (in different namespaces) so java -jar uberjars/worldsingles.jar runs a default process (built with depstar and -C -m worldsingles.publisher) but that JAR has dozens of -main functions and all those others are run via java -cp uberjars/worldsingles.jar clojure.main -m some.process for the various namespaces. That's mostly how we run our cron jobs, for example.

👀 3
Test This00:09:41

Awesome! Thank you. It is all much clearer now. I have so far always deployed using git pull. This has been really helpful.

seancorfield01:09:19

You're already doing things (one of) the Clojure way then 🙂

seancorfield01:09:59

(having the Clojure CLI installed on your servers can be useful for all sorts of ad hoc server maintenance tasks so that's definitely a path to consider)

seancorfield01:09:47

Oh, and one final thing about JAR files that may not be obvious: java -cp the.jar clojure.main with no additional arguments will start a plain console REPL, with everything in the JAR file as your available dependencies. That can be useful too at times.

Josef Richter08:09:15

Big Sur installation – can you guys help, please? I’m getting this error, despite having latest command line tools etc.

==> Installing clojure from clojure/tools
==> Downloading 
Already downloaded: /Users/josefrichter/Library/Caches/Homebrew/downloads/e7ac82ad2d40c6edab636f088e6ea8c92552eb07eb1736d0e36d55818e6bc4e7--clojure-tools-1.10.1.561.tar.gz
Error: Your CLT does not support macOS 11.0.
It is either outdated or was modified.
Please update your CLT or delete it if no updates are available.
Error: An exception occurred within a child process:
  SystemExit: exit

delaguardo08:09:19

this is a brew exception. check brew doctor it usually has some useful information.

Josef Richter08:09:17

ah, it tells me I’m out of luck, basically 🙂

delaguardo08:09:12

maybe check if xcode-select -p points to most recent command line tools

Josef Richter08:09:35

that gives me /Library/Developer/CommandLineTools

delaguardo08:09:02

but CLT for beta releases usually has “Beta” substring in path

Josef Richter08:09:14

I have clean install of Big Sur at the moment, so I shouldn’t have any older CLT.

Josef Richter08:09:09

I even downloaded the latest CLT published yesterday

delaguardo08:09:00

aha, then I can recommend to install xcode12 and point xcode-select to it’s CLT. Unfortunately apple forces beta users to install xcode to have up-to-date tools(

Josef Richter08:09:39

> point xcode-select to it’s CLT how do I do that please?

Josef Richter08:09:15

I mean, how do I find out where exactly to point it? should I see two separate installations of CLT?

delaguardo08:09:17

xcode-select -s /Applications/Xcode.app/Contents/Developer

delaguardo08:09:06

only replace http://Xcode.app with whatever you vave installed as Xcode12

delaguardo08:09:18

it could be http://XcodeBeta.app or something else

Josef Richter08:09:04

well at this point it’s actually official Xcode 12 distributed via appstore. It’s not beta anymore. Only Big Sur is beta. So I ran the command you gave me, which probably didn’t cause any change, and the problem unfortunately persists 😔

Josef Richter08:09:28

damn apple betas 🙈

delaguardo08:09:49

I was planning to upgrade this evening )

Josef Richter08:09:18

well be careful. I had to wipe clean my macbook and it took 6 attempts to install successfully 😄

delaguardo08:09:40

why wipe? is it required now?

Josef Richter08:09:34

my installation broke down and in recovery mode I was unable to unlock filevault, despite knowing the password. seems like very unfortunate bug… so the only way was to wipe clean and run clean install

Josef Richter08:09:50

luckily I have everything important in Dropbox, so no big deal

delaguardo08:09:12

good to know! will do a backup right before

👍 3
Josef Richter08:09:12

well, I don’t really have anything important in the first place 😄

Josef Richter08:09:50

they released beta7 today. so maybe it will be better again

delaguardo08:09:16

The Command Line Tools package for Xcode 12.2 beta isn’t currently available. (69012274) I found this in known issues for beta7 (

🙈 3
delaguardo10:09:54

hm… I think it is possible to install everything manually you need this https://download.clojure.org/install/clojure-tools-1.10.1.561.tar.gz unpack cd clojure-tools ./install.sh /usr/local

Josef Richter10:09:15

aah that opens its own set of problems with /usr/local not being writable, and cannot be changed

delaguardo10:09:53

I think prefix is not significant here. It could be any place available for you to write and added to your PATH

Josef Richter11:09:31

sorry for dummy question, where would you typically put it, please? it’s a bit low level for me

delaguardo11:09:27

do you have ruby installed?

delaguardo11:09:45

ruby --version should tell you that

Josef Richter11:09:18

yes, that comes with macOS I think

delaguardo11:09:26

mkdir -p $HOME/usr/local/Cellar/clojure/1.10.1.561
HOMEBREW_RUBY_PATH=$(which ruby) ./install.sh $HOME/usr/local/Cellar/clojure/1.10.1.561
ln -s "$HOME/usr/local/Cellar/clojure/1.10.1.561/bin/clojure" "$HOME/usr/local/bin/clojure"
ln -s "$HOME/usr/local/Cellar/clojure/1.10.1.561/bin/clj" "$HOME/usr/local/bin/clj"

✔️ 3
delaguardo11:09:54

then you need to add “$HOME/usr/local/bin” to your PATH

Josef Richter11:09:28

thank you. I fail here:

❯ ./install.sh $HOME/usr/local/Cellar/clojure/1.10.1.561
./install.sh: line 14: -pi.bak: command not found

delaguardo11:09:24

I changed my script to support that

delaguardo11:09:05

submit solution too early

delaguardo11:09:44

argh, you also need to run mkdir -p $HOME/usr/local/bin

Josef Richter11:09:22

you are the king!!!

Josef Richter11:09:40

thank you, seems to be running!

delaguardo11:09:20

Nice, glad that it is resolved) later, once everything will be settled on homebrew side, you can simply remove $HOME/usr

👍 3
👌 3
Josef Richter11:09:51

it all works like charm, running my tiny webapp smoothly now. thank you once again 👍

seancorfield16:09:01

This is why I stopped upgrading my Mac and stayed on 10.12 but now a lot of new software won't install because it's too old 😒 So I'm switching to Windows and WSL2. So tired of Apple breaking my dev environment!

seancorfield16:09:32

(and I've been an Apple customer for 30 years!)

delaguardo16:09:20

my mac survived 10.6 -> 10.15.6 of rolling updates of software + Air 2012 -> MacBook Pro 2019 updates of hardware but upgrade XP -> Vista took too much mental energy then I gave up on windows)

Josef Richter16:09:53

@seancorfield it’s not that bad, it’s just not a good idea to install beta versions of operating systems if you want to be conservative 🙂 so I knew and embraced the risks. but if you find peace and pleasure on windows, then why not. microsoft is making huge leaps in developers’ satisfaction.

seancorfield16:09:21

@U04V4KLKC I've only become a fan of Windows since 8.1 (but I've been forced to use every version since 3.11). I've always had Macs. I started on 680x0 chips, went through the PowerPC migration, the Intel migration. I've had over half a dozen laptops and over half a dozen desktops. Back in the System 6 & 7 days I ran MachTen (Tenon Intersystems' BSD variant) as a "parasitic" Unix O/S alongside Apple's O/S so I've always had the "Mac" interface and *nix under the hood. But almost every single O/S upgrade broke something in my dev setup and over the last 5-8 years Apple seem to have lost interest in developers and swung their focus squarely to consumers and services. I used to absolutely hate MS but over time I found myself preferring their apps on my iPhone, and I bought a cheap Windows laptop in 2012 that has slowly become a more usable developer machine. This year I replaced my iPhone with an Android, and I just bought a Surface Laptop 3, and I hadn't realized just how much of a "captive system" Apple has become. I'm pleasantly shocked at how much MS has embraced open source and how much they are focusing on developer experiences these days.

seancorfield16:09:49

With WSL, I run Ubuntu for all my dev work on both laptops, while still having the slick UX of Windows 10. I never thought I'd switch from a Mac, when I bought my last iMac in 2012...

delaguardo16:09:18

I always have archlinux aside from other, more user friendly, OS. That is like steel hardening - I saw so many problems during software upgrade, now they don't bother me at all) maybe you right and Apple is not doing great job for developers, but I just can't share same feeling, probably because of some different usage patterns. But let's do not convert this thread into a battle of vendors our operating systems. Someone had annoying problem, was aware of consequences of beta testing and ask for help - I would never try to convince trying something completely different because of such a minor thing) :cheers:

seancorfield17:09:58

I should try Arch some day. I hear lots of developers using it and liking it.

Panagiotis Mamatsis08:09:10

Good morning everyone! Nice to meet you all! I want to start learning a new language and I am thinking about Clojure. I have functional programming background. The purpose of learning it is to use it in my existing company's stack. We are a JVM company. Any good book for the newcomer?

practicalli-johnny09:09:50

https://practicalli.github.io/ has books and 80+ hours of videos to help learn the Clojure language and development with Clojure. Content regularly added... Feel free to ask questions about the content in #practicalli

alpox08:09:24

@pmamatsis as I heard there are many good ones. I really liked https://www.braveclojure.com/ and you can read it online for free

michele mendel08:09:15

I can also recommend Brave Clojure. I also read https://pragprog.com/titles/roclojure/getting-clojure/, but it doesn't go as deep in the material as Brave. Next book will be https://pragprog.com/titles/vmclojeco/clojure-applied/, since it talks about working on a project.

alpox08:09:31

@michelemendel oh clojure applied looks great! Gotta put it on my to-read list

Panagiotis Mamatsis11:09:59

Thank you all so much for your answers! I will be starting on tonight! Please bear with me! I will come back with more questions! :)

Test This12:09:03

@pmamatsis I am beginner to clojure too. Since you know Java you are likely in a much better situation than I. But the two I liked are: Clojure Workshop and Living Clojure.

michele mendel12:09:47

I also recommend listening to Eric Normand’s podcasts.

Michael J Dorian13:09:19

Hey friends, is it typical when building a clojure API to pass data to the (also clojure) frontend as json, or plaintext? Or, is there a way to do edn directly?

Michaël Salihi13:09:16

Hi! I want to share this GIT repository that might be useful for beginners. This is the useful usermanager example of @seancorfield (https://github.com/seancorfield/usermanager-example/) but for which for learning purposes, I started again from scratch by replacing the Compojure librarie by Reitit and Component by Integrant : https://github.com/PrestanceDesign/usermanager-reitit-integrant-example Cheers!

3
rmxm13:09:06

So im doing some refactoring work(mostly Im trying to remove unwanted dynamism). I started to run into glorious cyclic dependency problem. It is kind of prominent in clojure since namespaces get initialized with require -> for instance in python import only references a namespace, it gets initialized on first access/invokation from that ref. Is there any sane way of dealing with stuff like this?

alpox14:09:46

@roguas Im pretty new to clojure but I believe the problem is often the same. Cyclic dependencies often show that things depend on each other which shouldnt. One way this is often tackled is by using a separate namespace for protocols and alike

rmxm14:09:23

Ive read some discussions regarding this and somehow understand certain underlying problems with that. Problem is that some things are in fact reliant on each other in this way and it is kindof pointless to redesign for compilation only. Like in the python example, you can do

a.py import b
b.py import a

alpox14:09:56

Yes, that does not work in clojure or as I had the problem too, in golang. One part of me tells me this is good though. The redesign paid out in the end as things got decomplected

alpox14:09:48

The general way to go is then that different namespaces talk to each other over abstractions from a separate namespace and things which really belong together go in the same namespace

rmxm14:09:57

Is it possible to do require during runtime? I am trying but keep getting Unable to find static field

dpsutton14:09:38

what are you trying?

rmxm14:09:01

@dpsutton so currently I can kick the can down the road, by defering requiring a namespace into function. Since the namespace I want available has only one symbol/definition.

; a.clj
(ns a)

(def y 3)
(defn get-x [] 
  (require `(b))
  b/x
)

; b.clj
(ns b (:require [a :as a])
(def x 2) 

rmxm14:09:48

I am trying to avoid this problem as it currently requires too much refactoring for the chunk I am about to clean up. So I want to defer namespace initialization till a function gets invoked.

rmxm14:09:57

This way, whenever a/get-x gets called b will already by initialized (so skipped)

dpsutton14:09:32

someone had a similar problem yesterday. require is runtime but still clojure is trying to compile b/x and there is no b. you can do (requiring-resolve 'b/x) (where b/x is the fully qualified name of x) or, if you're sure that b has been loaded you could just use the fully qualified path. both of these are hacks around the design and fixing that would most likely be your best bet

rmxm15:09:33

Still kinda stuck is this the way to go?:

; a.clj
(ns a)
(def y 3)
(defn get-x [] 
  (requiring-resolve 'b/x)
  b/x
)

dpsutton15:09:54

check the docstring on requiring-resolve

dpsutton15:09:17

it returns the var and then you would use it. (let [f (requiring-resolve 'b/x)] (f))

rmxm16:09:51

thanks, (var-get) got me to goodworkable place

Panagiotis Mamatsis14:09:57

Can someone please explain what is the difference between actors and agents? I can't quite understand the difference.

Alex Miller (Clojure team)14:09:19

probably helpful for perspective

stopa15:09:32

If I eval a symbol without any namespace: (eval '>) (defn f [] (+ 1 1)) (eval 'f) To my surprise, eval does the right thing: it finds clojure.core/>, and finds my.namespace/f Is there an article on how clojure resolves symbols? Would love to read it!

Alex Miller (Clojure team)15:09:20

well Clojure wouldn't work if eval didn't do the right thing, so that should be surprising :)

😆 3
Alex Miller (Clojure team)15:09:58

you are always in a current namespace per *ns*

❤️ 3
Alex Miller (Clojure team)15:09:15

that namespace has a set of "referred" names, aliases, etc

Alex Miller (Clojure team)15:09:53

(find-doc #"ns-") to find a bunch of functions that let you explore that info (ns-refers etc)

Alex Miller (Clojure team)15:09:32

importantly clojure.core is auto-referred by ns so everything in core is always available unqualified

❤️ 3
stopa15:09:24

Ah, I see, thanks Alex! Will poke around with (find-doc #"ns-")

JL16:09:37

Question for the Gurus out there - building a simple API that pulls users info from Datomic. Function in question is this one: First run it returns everything fine, if the database is updated via another call and this function is run again - it seems to be returning cached results. How do I get it to re-evaluate it each run?

(defn find-user-by-username-or-email-all [db username-or-email]
  (d/q
   '[:find (pull ?e [*]) .
     :in $ ?user-or-email
     :where (or [?e :user/username ?user-or-email]
                [?e :user/email ?user-or-email])]
   db
   username-or-email))

(defn getUser [username-or-email]
  (let [id (find-user-by-username-or-email-all db username-or-email)]
    {:id id}))
{
  "id": {
    "user/email": "john...",
    "user/password-reset-sent": "...",
    "user/username": "john",
    "user/send-updates?": false,
  }
}
Now we update username to something else and the getUser still returns:
{
  "id": {
    "user/email": "john...",
    "user/password-reset-sent": "...",
    "user/username": "john",
    "user/send-updates?": false,
  }
}

marshall16:09:11

@thdm are you passing the same db value each time?

marshall16:09:25

Datomic db values are immutable

JL16:09:25

Yes so the input in this case would be (getUser "john")

marshall16:09:37

So to query for data that has been added since your first call to d/db you need to call it again to get a new db value

JL16:09:31

I'm not understanding - I have to call the getUser twice?

marshall16:09:53

No, the call to d/db

marshall16:09:59

To get a database value

marshall16:09:12

The db you're passing into the query

marshall16:09:10

Give me a minute I'll make an example

JL16:09:26

db is defined as:

(def uri (:datomic-url e/env))

(def conn (d/connect uri))

(def db (d/db conn))

marshall16:09:47

Right. That value of db is immutable

marshall16:09:12

It is a db value as of the exact moment you called d/db

marshall16:09:52

So anything you transact into the db after that point in time will not be in that original db value

JL16:09:37

so I have to redefine db each call then?

marshall16:09:39

It depends what you're doing. You may want to keep a specific db value for several operations

marshall16:09:04

So that you know they're all using the same consistent point in time db value

JL16:09:29

aww ok - got a code snippet to re-evaluate db each call? LOL omg.

JL16:09:44

In this case I want the latest - basic CRUD operations

marshall16:09:27

if that's the case, you can pass (d/db conn) to your call to your query function

marshall16:09:30

instead of passing db

JL16:09:10

So like this?

(defn getUser [username-or-email]
  (let [db (d/db conn)
        id (find-user-by-username-or-email-all db username-or-email)]
    {:id id}))

marshall16:09:19

sure that works too

marshall16:09:25

i might still make db an argument

marshall16:09:32

otherwise you'd have to pass a conn

marshall16:09:36

which is less idomatic

JL16:09:40

Ok I get where I went wrong

marshall16:09:44

so leave your getUser function alone

marshall16:09:04

and just change where you call it from: (getUser db ...) to (getUser (d/db conn) ...)

marshall16:09:53

ah, sorry i misread your original; That advice ^ still stands though

marshall16:09:12

but having getUser do the "getting a db value" is probably OK too

marshall16:09:19

is your conn a global var?

marshall16:09:44

generally you'll want to avoid that and pass either a conn or a db value to functions that transact or query, respectively

marshall16:09:21

i.e. maintain the conn in a map that represents your system context (or use something like the component library to help with it)

marshall16:09:34

then your getUser could take a db and a username

marshall16:09:07

and you could invoke with something like: (getUser (d/db (:conn my-ccontext-map)) "username i care about")

JL16:09:55

Re-arranged it to

(defn find-user-by-username-or-email-all [db username-or-email]
  (d/q
   '[:find (pull ?e [*]) .
     :in $ ?user-or-email
     :where (or [?e :user/username ?user-or-email]
                [?e :user/email ?user-or-email])]
   db
   username-or-email))

(defn getUser [db username-or-email]
  (let [id (find-user-by-username-or-email-all db username-or-email)]
    {:id id}))

(GET "/getuser/:n" []
  :path-params [n :- s/Str]
  ;:summary ""
  (ok (getUser (d/db conn) n)))
)))

marshall16:09:20

:thumbsup: looks much better consider looking into conn management stuff in the future (i.e. the conn not being a top-level var, but instead created by a function call and passed around as needed) see: https://github.com/Datomic/ion-starter/blob/master/src/datomic/ion/starter.clj

marshall16:09:55

both the peer library and client library cache connections, so you can safely write a "get connection" function that you can invoke from wherever in your application

marshall16:09:00

and you will get back the same connection

marshall16:09:46

there is a little bit of extra stuff in that example ^ for ions But you could just as easily have a get-conn function:

(defn get-conn
 (d/connect "my-datomic-uri"))
in a peer

JL16:09:33

Gotcah - Thanks a ton - that solved a couple days of head pounding.

james19:09:24

Hi! I was wondering if there is a way to get left-over keys from an s/keys spec. For example, if I have (s/keys :req-un [::name]) and the map {:name "Alice" :age 34}, and if it's s/valid?, I would like it to return {:age 34}. Is there something in Spec that does this? (I don't want to prohibit extra keys, but I'd like to know what they are, if provided.)

james20:09:00

(I was able to hack something together using s/describe.)

vncz20:09:22

@U0186LK0G87 I'm curious about your solution, can you post it?

james20:09:40

Sure. But it's terrible. And also doesn't handle req-un vs req and opt-un vs opt.

(defn all-keys
  "If given SPEC is a keys spec, return all the keys it knows about."
  [spec]
  (let [form (s/form spec)
        [kind & options] (and (seq? form) form)]
    (when (= kind 'clojure.spec.alpha/keys)
      (set (mapcat (apply hash-map options)
                   [:req-un :opt-un :req :opt])))))

(defn leftovers
  "If SPEC is a keys spec and X conforms to it, return left over keys."
  [spec x]
  (when (s/valid? spec x)
    (when-let [keys' (all-keys spec)]
      (let [extra-keys (set/difference (set (keys x)) keys')]
        (zipmap extra-keys (map x extra-keys))))))

dehli21:09:39

There’s also https://cljdoc.org/d/metosin/spec-tools/0.10.4/api/spec-tools.core#select-spec which does the opposite of what you need. It’s a pretty cool library!

👍 3