Fork me on GitHub
#beginners
<
2022-10-27
>
Tirion02:10:49

Hi all, Im having an issue with Stuart Sierra's component framework. Basically I have 2 components where one depends on the other. The first component makes an http call, gets a value, then stores it into an atom. The second component then uses that value stored in the atom to perform a task. In the start and stop methods for both components I am returning 'this' because I am not modifying the components. The issue I'm having is that after the task is performed the application seems to try to start up again, resulting into erroring out because the address is already in use. I believe I'm doing something wrong with the components. Has anyone faced this issue and how did you solve it?

hiredman02:10:36

Components don't do anything unless you call functions on them, there is no magic in there. You should take another look at your code, if you get an exception the stacktrace will tell you what code is on the stack and if component's start function is on the stack, it will give you a file and line number where it is called

hiredman02:10:16

My guess though, since your start does nothing is that your stop does nothing, so the http server from the first time you ran start is just hanging around using the port, and the "starting again" errors are actually just the errors starting

Matthew Twomey02:10:25

I’m doing a very simple test in which I’m requiring a .gitlib style library:

com.grzm/awyeah {:git/url "" :git/sha "1810bf624da2be58c77813106a1d51e32db11690" :git/tag "v0.8.35"}
My “app” right now is simply:
(ns core (:gen-class))

(require '[com.grzm.awyeah.client.api :as aws]
         '[com.grzm.awyeah.credentials :as credentials])

(defn main [opts]
  (println "Hello world!"))
However, when I try to execute it with clj -X core/main I get an error about clojure/tools/logging and I am not sure why:
Execution error (FileNotFoundException) at com.grzm.awyeah.client.api/eval228$loading (api.clj:4).
Could not locate clojure/tools/logging__init.class, clojure/tools/logging.clj or clojure/tools/logging.cljc on classpath.
Can anyone help point me in the right direction?

hiredman02:10:47

awyeah-api doesn't declare any top level dependencies

Matthew Twomey02:10:21

Is there a “standard set” that normally get delcared?

hiredman02:10:27

Just deps in aliases, but for transitive deps tools-deps only looks at top level deps

hiredman02:10:51

So to tools deps it appears that the library doesn't need any dependencies

hiredman02:10:12

So the dependencies it needs are missing when you try to use it

Matthew Twomey03:10:19

Ok. I see when I added tools.logging to my edn, it gets past that error and now has an error for async. If I want to continue this, is the best thing just go through the errors and add them - or is there a way to know what all should be added?

hiredman03:10:31

Likely the clj alias in awyeah-api has all its deps

Matthew Twomey03:10:07

Ok I check that out. Thanks sir - this should be enough of a hint to get me going.

Matthew Twomey03:10:57

Yep - you are exactly right, thanks!

hiredman03:10:27

You awyeah is intended to be a drop in replacement for another library in babaska scripts, if you are not using babaska scripts you can just use the aws-api library

Matthew Twomey03:10:55

I actually am using babashka, but what I am trying to achieve is: I’d like to create few “helper” libs for my AWS work. These libs I intend to use from babashka (just to keep my babashka scripts more simple / cleaner).

Matthew Twomey03:10:33

But in order for my libs to work in babashka, I can’t use the main cognitect api lib in them.

hiredman03:10:03

Sure, for whatever reason the author of the lib hasn't run into issues with his deps.edn, likely because they never use it as a library outside of babaska

Matthew Twomey03:10:10

yeah makes sense. I like the tool, it’s a great solution fo smaller scripting. Just trying to learn all this stuff as I’m fairly new to clojure 🙂

Matthew Twomey04:10:51

Just dropping a hint here for anyone that searches this at a later date: I got my lib fully working now in babashka, the final missing piece was getting the .gitlibs dep into the classpath. For that, I used Sean Corfield’s build-clj (https://github.com/seancorfield/build-clj) with the :transitive option.

Matthew Twomey05:10:42

I’ve got some code defined in an edn file: :basis (b/create-basis {:project "deps.edn"}) In my function I want to evaluate that code. So I’m trying: ((:basis opts)) (opts is that map). I’m getting: class clojure.lang.PersistentList cannot be cast to class clojure.lang.IFn I’m sure I’m missing something simple here?

seancorfield06:10:24

If you have code in EDN, you will need to eval it after reading it -- otherwise it is a literal list of symbols etc.

seancorfield06:10:32

What you have above is like {:basis (list 'b/create-basis {:project "deps.edn"})}

seancorfield06:10:16

So (:basis opts) is a PersistentList. And ((:basic opts)) is like ((list 'b/create-basis {:project "deps.edn"})) so you're trying "call" a list of data.

Matthew Twomey06:10:34

Ahh I see -ok. I just tried with eval Yes! That works. Thank you very much, really appreciate it!

Matthew Twomey06:10:12

Actually lol - what I’m doing is directly working with your library - build-clj lol. Such a small world, thank you for the pointer 🙂

seancorfield06:10:05

Things that seem to require eval should be viewed as a "design smell". I wouldn't put code in the EDN, I would only put data, with enough annotations to be able to apply code to that data after it is read in.

seancorfield06:10:51

So perhaps :basis-arg {:project "deps.edn"} and then after reading that in: (b/create-basis (:basis-arg opts)) in your code.

seancorfield06:10:07

You can put a fully-qualified symbol in EDN and then, after reading it in call requiring-resolve on it to get it loaded and get its value.

Matthew Twomey06:10:35

Aaaaaah ok. You’re reading my mind. That’s exactly what I’m trying to do. I’m looking at: “if you want your user deps.edn included, you will need to explicitly pass :basis (b/create-basis {:user :standard}) into tasks,” (from your github) and I was trying to figure the best way to do that (again new to clojure).

Matthew Twomey06:10:53

Ok - these suggestions make perfect sense, I will give that a go!

seancorfield06:10:06

So {:basis-fn clojure.tools.build/create-basis :basis-arg {:project "deps.edn"}} and then do ((requiring-resolve (:basis-fn opts)) (:basis-arg opts)) but at that point I might question your sanity 🙂

Matthew Twomey06:10:44

ha ha ha - ok. I’m trying to work out if I’m better off having a “generic” build.clj and putting everything specific in deps, or if I should have tailored build.clj s’ per-project.

seancorfield06:10:44

Yeah, that means: you need a task to call (b/create-basis {:project "deps.edn"}) and add that to hash map of data under the :basis key that it then passes to one of the build-clj tasks.

seancorfield06:10:00

You only want deps (and maybe some pure data) in deps.edn -- not code.

Matthew Twomey06:10:44

I was also doing this to keep config values:

Matthew Twomey06:10:44

Thoughts on that?

Matthew Twomey06:10:58

(notwithstanding the code still in there)

seancorfield06:10:04

Please post code/data, not images.

seancorfield06:10:15

Images are not accessible to all people.

Matthew Twomey06:10:24

:exec-args {:transitive true
                               :lib 'com.beakstar/aws
                               :version "0.1.0"
                               :basis (b/create-basis {:project "deps.edn"})
                               :target "target"}}}}

seancorfield06:10:43

Yeah, you can't have code in :exec-args. Just data.

Matthew Twomey06:10:02

I was keeping :exec-args in deps to store things like versions lib name, …etc.

Matthew Twomey06:10:11

Do you think that will get me into trouble, or that ok?

seancorfield06:10:14

That's fine -- we do that at work.

Matthew Twomey06:10:57

Ok - thanks, I really appreciate your time. This was all very helpful. I think I’m on a better track now.

seancorfield06:10:01

We use a standard alias in each project deps.edn file like this:

:uberjar
  {:uber-file "../../../build/uberjars/api-1.0.0.jar"
   :main ws.api.main}

seancorfield06:10:39

and then we build the basis for the project and have code to pull the alias as data from that...

Matthew Twomey06:10:20

Ok that makes sense. There are so many options, trying to get me head around things.

seancorfield06:10:31

(defn- get-project-aliases []
  (let [edn-fn (juxt :root-edn :project-edn)]
    (-> (t/find-edn-maps)
        (edn-fn)
        (t/merge-edns)
        :aliases)))
Ah, so we just use t.d.a. to read the EDN files and pull the aliases from them. Not the basis. But we use the basis "as usual" for a given project.

seancorfield06:10:19

Here's part of a task that walks over multiple projects in our monorepo and builds uberjars for each one:

(doseq [p projects]
      (println "\nRunning: uber on" p)
      (let [project-dir (str "projects/" p)
            aliases     (with-dir (io/file project-dir) (get-project-aliases))]
        (binding [b/*project-root* project-dir]
          (bb/clean {})
          (bb/uber (assoc (:uberjar aliases)
                          :compile-opts {:direct-linking true}))
          (bb/clean {}))))

Matthew Twomey06:10:49

Oh nice, thank you. I’m not using a mono-repo, but I went to build-clj because I needed to package some .gitlib repos and through searching this slack, I saw your message about the :transitive option, which solved a big problem for me.

seancorfield06:10:19

The :exec-arg stuff only makes sense if you are invoking via -X from the command-line.

seancorfield06:10:42

But you can use arbitrary aliases as pure data, and use t.d.a to read/merge and extract :aliases and have access to all of that.

Matthew Twomey06:10:24

What is t.d.a? (sorry)

Matthew Twomey06:10:12

tools deps alpha?

seancorfield06:10:09

Yes (sorry). I tend to use it for a lot of low-level stuff.

Matthew Twomey06:10:17

ok thanks. Ok - I was invoking via -X, but only because I was testing / trying to figure it all out. But I didn’t realize I could use aliases as pure data. That would be even better. I’m going to give that a try as well.

seancorfield06:10:22

If you have a basis, you can use t/combine-aliases to apply those aliases to it. If you have a deps.edn file (as a java.io.File), you can use t/slurp-deps to read it according to all the built-in rules to produce data as a shortcut. etc.

seancorfield06:10:34

Feel free to ping me with any further Qs you have about build-clj via DM if you want. It's lucky that I happened to notice the Q in #C053AK3F9 because it's fairly high-traffic. There's #C02B5GHQWP4 for build.clj / tools.build Qs (i.e., the stuff below my wrapper library).

Matthew Twomey06:10:12

Thanks sir, again - really appreciate you spending a little time with me here. I helps to understand my options and some best practices as I’m getting spun up with clojure and the ecosystem.

James Amberger13:10:59

is there any difference between (vector a b c) and [a b c]

ghadi13:10:18

functionally no. but they go through minorly different codepaths

dpsutton14:10:01

they cannot always be swapped for each other:

user=> (let [x 1] x)
1
user=> (let (vector x 1) x)
Could not resolve symbol: x [at <repl>]
Not sure if this is a good answer or not though.

2
James Amberger14:10:34

when do you need (vec) ?

teodorlu14:10:41

Vec is different! It takes a single argument - a collection - and turns it into a vector. Vec let's you turn a list into a vector, a map into a vector, you name it :) More on vec: https://clojuredocs.org/clojure.core/vec

teodorlu14:10:08

(vec coll) Is sort of like (apply vector coll) If you're familiar with apply.

Ferdinand Beyer16:10:27

You’ll often encounter “constructors” like vector, list and hash-map in function literals:

(map #(vector % (inc %)) (range 10)) ; works
(map #([% (inc %)]) (range 10))      ; ERROR

Ben Sless17:10:03

List is special though

teodorlu08:10:34

> List is special though > Any idea why?

Ferdinand Beyer08:10:15

Maybe @UK0810AQ2 meant that (list 1 2 3) and (1 2 3) is not the same, as list literals are evaluated as function calls and list has implicit quoting, like '(1 2 3)

Ben Sless09:10:52

I meant that list goes through Primordial List which is a special case

Or Halimi16:10:44

Hi guys, I start learning and I wonder what have I done wrong here. calling greeting without parameter returns only hello -_^

(defn greeting 
  ([] greeting "hello" "world")
  ([x] greeting "hello" x)
  ([x, y] str x "," y)
 )

(assert (= "Hello, World!" (greeting)))
(assert (= "Hello, Clojure!" (greeting "Clojure")))
(assert (= "Good morning, Clojure!" (greeting "Good morning" "Clojure")))

Ben Sless17:10:25

the forms in the function body are not function calls, needs to be (greeting ,,,) not greeting ,,,

Or Halimi17:10:29

Oh, so I need to warp the body with () too? Thanks!

dpsutton17:10:32

you need to wrap all function calls in parens

Or Halimi17:10:35

So without function call it’s ok?

(defn echo [x] x) => valid?

dpsutton17:10:52

that just returns x which is fine.

Or Halimi17:10:38

Thanks, Need to get used to the syntax

Ben Sless17:10:06

The syntax for lists: function application, macro application, special form OR function arity implementation (only in function body)

Chris G17:10:15

are you trying to overload the func?

Ben Sless17:10:41

The syntax is very regular. Everything you want to happen, you put in a list (not a vector). The operator is always the first argument of the list

Ben Sless17:10:57

Bindings are always (?) in vectors

🙏 1
Ben Sless17:10:14

Examples of bindings: Function parameters: (defn foo [a b c] ,,,) Let bindings: (let [a 1 b 2 c 3] ,,,) with-open (with-open [w (io/writer (io/file "outfile"))] ,,,) with-rederfs (with-redefs [qualified/symbol (fn [x ,,, z] ,,,)] ,,,) for comprehension (it's not a loop) (for [x xs y ys] ,,,) loop (is a loop) (loop [a 1] (recur (inc a))) doseq (doseq [x xs] (println x))

1
Chris G17:10:45

this example may help with what your trying to achieve

(defn greeting
    ([]
     (greeting "Hello" "world"))
    ([x]
     (greeting "Hello" x))
    ([x y]
     (str x ", " y)))
https://archive.clojure.org/design-wiki/display/design/Function%2Boverloading.html

Ben Sless17:10:06

In every example of binding form besides function parameters, bindings are pairs of left-hand-side name and right-hand-side expression The RHS expressions are evaluated sequentially, so a subsequent RHS expression can use the bound name from a previous LHS binding form

Or Halimi17:10:19

@U03LTP449RS Yea that’s what I end up with, thanks 🙂

🙌 1
Michael Stokley18:10:58

i'd like to use criterium library to benchmark database calls. however, i'm worried that criterium will send a billion calls. is there an option i can pass to limit the number of executions? or the overall load?

hiredman18:10:41

criterium is really for cpu bound stuff

Michael Stokley18:10:34

that makes sense to me

respatialized19:10:13

You're better off using a trace library for this; safely and mulog are both really good (the former builds on the latter). You can write the logs to an EDN file or cloud log publisher for easier analysis after the fact while your app runs and get observability into real-world performance. For controlled testing, throttler is a good library that lets you rate limit functions in a very intuitive and low overhead way.