Fork me on GitHub
#beginners
<
2021-05-18
>
gorjusborg00:05:21

anyone know of any good documentation on how deps.edn works?

gorjusborg00:05:44

i don't mind reading the code either

gorjusborg00:05:59

but not sure where it lives

gorjusborg00:05:22

thanks

👍 3
Franco Gasperino04:05:56

Any options on using a var binding as a require arg, instead of a literal?

phronmophobic04:05:10

do you have an example of what you're trying to do?

Franco Gasperino04:05:30

(def config {:namespace "clojure.string"})
(let [n (:namespace config)]
  (when (n) (:require [n :as module])))

Franco Gasperino04:05:59

n is a var, require wants a list/vec of literals

hiredman04:05:09

n is "local" not a var

Alex Miller (Clojure team)05:05:09

not sure what you're actually trying to do, but requiring-resolve might help

phronmophobic05:05:22

fwiw, the following works:

(let [config {:namespace "clojure.string"}
      n (:namespace config)]
  (when n (require [(symbol n) :as 'module])))
(module/lower-case "CLOJURE")
but you probably want something like requiring-resolve

Franco Gasperino05:05:31

thanks for the feedback

Franco Gasperino06:05:20

im looking for behavior parity between an existing python code base and what can be accomplished here. The python project leverages importlib and dynamic loading in a fashion somewhat similar to java spring ioc, interfaces, and varying implementations.

Franco Gasperino06:05:48

repl testing looks as if requiring-resolve can provide at least the edn config ->conditional import path here.

phronmophobic04:05:24

like this?

(let [str 'clojure.string]
  (require str))

Juλian (he/him)08:05:40

Is there a preferred way to add support for multiple languages? For example, use maps like {:en "Hello, World", :de "Hallo Welt!", etc...} instead of strings and then let the display function choose depending on chosen language?

pithyless10:05:37

@U020G9UCM1N In case you're not familiar with this domain, you can find a plethora of stuff to read about searching for internationalization and localization (abbreviated as i18n and l10n ). There is a lot of nuance to doing this well at scale. But if you're just looking for something quick to get you started grab an existing i18n library (e.g. https://github.com/ptaoussanis/tempura) and try not to go down the rabbit hole of learning everything about this - the well runs very deep. :)

Juλian (he/him)10:05:23

That looks nice, thanks!

Juλian (he/him)22:05:52

small question about this and keywords in general: if I nest the IDs, like :example {:foo {:bar "foobar"}} in the dict for tr, according to the docs, I should refer it using :example.foo/bar - my first thought would have been :example/foo/bar and it works, as does :example.foo.bar , but those two are illegal keywords according to https://clojure.org/reference/reader - is there a way to transform a keyword by adding prefix/suffix? like in this example, how can i get from :foo to the :example.foo/bar ?

Juλian (he/him)22:05:17

there has to be a better and safer way than (keyword (str "example." (name :foo)) "bar") (with :foo coming from a parameter). to give more context, I want to put all item names for my game into that dictionary, nested under :item - so for example :item {:sword {:name "Sword", :description "A Sword with a sharp blade."}} and when outputting the name of the item, I would have :sword and want to call tr with :item.sword/name - is this a valid approach or should I rethink it?

pithyless05:05:33

Usually, keyword namespaces are not meant to be programmatically manipulated and interpreted in "parts". This is a subject that comes up once in a while e.g. in clojure.spec discussions, but my understanding is that this is not something the Clojure Core team feels is a good practice, so you won't find a lot of clojure.core functions to manipulate the namespace as a hierarchy of segments. To not go against the grain, you can ask yourself if your request should be asking for the name of item :sword or simply should be asking for the :item.sword/name? The latter would make it unambiguous and no need for the monkeying around with strings; the former and writing some helper function to hide (keyword (str/join "." ["item" (name item)]) "name") is probably as good as it gets.

Juλian (he/him)09:05:04

Thanks again! I think I'll avoid nesting too deep, then. Maybe even use a separate dictionary for each category, like one for item names, one for item descriptions. Then I can use the keyword for the item as resource id (in this example, :sword).

pithyless11:05:54

My gut reaction would be to have :item/sword or even :item.weapon/sword wherever you're dealing with the thing and then use the keyword/string logic to map all translations (so you automatically map :item/sword to {:item.sword/name ".." :item.sword/description ".."})

pithyless11:05:31

Having unambiguous full-qualified keywords as identifiers means you can mix-and-match maps easier and pass things around without nesting for context. It seems like a small difference, but it has some nice long-term benefits that are not easy to articulate until you start growing bigger systems with it.

pithyless11:05:16

Check out https://www.youtube.com/watch?v=IS3i3DTUnAI if you want to learn more about the perks of fully-qualified keywords

pithyless11:05:25

{:item/id :magic-sword-of-parens ;; or more unique ala :
 :item/kind :item.kind/weapon
 :item/weapon :item.weapon/sword
 :item/name "this is dynamically generated based on translation, but is generic key for UI"
 :item/description "same here"} 
^ Perhaps too much ceremony for you, but notice that all those keywords can be used in different parts of the codebase without any additional context and data - and you know exactly what they are.

practicalli-johnny09:05:15

Any suggestions as to why ( "config.edn") is returning nil even though the config.edn file is on the classpath, as shown by lein classpath command? Its an older Clojure project with Leiningen. I tried using Java 8 and Java 11, a few different versions of Leiningen. I can slurp the file...

Juλian (he/him)10:05:24

I load a few files using io/resource, they all are in the resources folder

practicalli-johnny11:05:14

I can use io/resources in a newly created project. There is something strange in this legacy project that is messing with the class path I think.

noisesmith13:05:35

try lein with-profile +repl cp to get the classpath a repl would use

noisesmith13:05:44

you can swap in other profiles of course

noisesmith13:05:08

also for io/resource to work, the document shouldn't be on classpath, its parent folder or zip file / other container should be

noisesmith13:05:29

nb. "." shouldn't be on classpath, you should have specific subdirectories of the project like src and resources that are on classpath

practicalli-johnny09:05:06

Is there a way to see the classpath from the REPL ??

jumar10:05:41

What I use most often is cider-open-classpath-entry There's also a system property for this:

(System/getProperty "java.class.path")

👍 3
practicalli-johnny11:05:21

Hmm, this just shows the leiningen jar as the class path. very strange.

jumar12:05:18

It works fine for me. I’ve been using it for a long time from jdk 8 to jdk 16. Note that your code fails completely on modern JDKs

practicalli-johnny13:05:32

Yes, the code does work correctly in a new project. It gives the leinigen path in the older project, I think because something is failing... Thanks.

practicalli-johnny10:05:47

I have tried some variations around this example, https://gist.github.com/ekoontz/1502838 but I get nil returned. I though the code wasnt working, but perhaps Leiningen is not correctly setting the classpath for the repl, which would explain the issue

practicalli-johnny10:05:07

Curious that this code returns the leiningen jar as the class path, rather than the really long classpath I get when using lein classpath command

(doseq [url (seq (.getURLs (java.lang.ClassLoader/getSystemClassLoader)))]
       (println (.getFile url)))

practicalli-johnny10:05:14

Definitely something in the Leiningen project messing up the classpath when running the repl either on the terminal or via the editor. I think Leiningen has been silently failing to build the classpath. Also it seems Leinigen was not downloading all dependencies.

jumar12:05:32

Just in case: it’s always good to try lein clean if you experience weird issues

noisesmith13:05:14

if it was "failing to build a class path" java wouldn't even be able to run anything. what is happening is you are asking for a different classpath than you think you are.

noisesmith13:05:53

> it seems Leinigen was not downloading all dependencies. that makes me think that something replaced the classpath / dep vector where it should have been adding to it (look for the ^:replace metadata in a profile for example)

practicalli-johnny15:05:45

The project does something intricate with project.clj and profiles.clj (which is also in the root of the project). I commented out all the bits I didnt understand and at least I have a proper class path now. I also decided to add a deps.edn file and use Clojure CLI tools. This showed some issues with transitive dependencies which I've now resolved.

noisesmith14:05:18

$ lein deps :tree also helps diagnose transative dep problems, and $ lein help sample has example based docs of most of lein's weird features

practicalli-johnny18:05:54

I found an issue with a transitive dependency of a transitive dependency. There were also issues with a user.clj file, which was automatically loaded on startup by Leiningen. The repl runs now, thanks all.

noisesmith19:05:15

leiningen does not autoload user.clj, but clojure does (if it's seen on classpath) - perhaps the difference was lein putting the file on classpath

noisesmith19:05:37

a transitive dep of a transitive dep is a transitive dep (by the transitive property :D)

👍 4
pavlosmelissinos11:05:44

(defn my-fn [{:keys [unparam1 unparam2]
              :param/keys [param1 param2 param3]
              param-type :param/type
              :as params}]
...)
I have a function that looks like the above. Is there a more intuitive way (the change of the order throws me off quite a bit) to rebind ::param/type to param-type? The goal is to avoid having to use the name type to refer to this parameter within the scope of the function. I think it would be nice to be able to say something like {::param/keys [param1 [type :as param-type]]}

Krishan V14:05:30

Hey everyone, is there a concept of a spread operator in Clojure? I want to merge a map with a sequence of maps, but I want to 'spread' out that sequence.

Erik B Good16:05:24

Hi, I was wondering if there was any way of making some simple dfs traversal work with recur? I understood that recur uses tail optimisation which is not otherwise possible given the JVM. Now , if I simply replace my recursive call to dfs, I get an error 'Can only recur from tail position'. Is there a clojurian way of making this sort of code work with tail optimisation? Thank you very much for your help. (*fn* dfs [tree traversed] (*let* [node (first tree) c1 (second tree) c2 (nth tree 2)] (*if* (nil? tree) (conj traversed tree) (concat [node] (dfs c1 traversed) (dfs c2 traversed)))))

noisesmith16:05:21

in order to tail recur over a tree you need to lift the rest of the tree traversal into a data structure, which leads to harder to read code. except when you have extreme efficiency needs, the right thing to do is just consume stack proportional to the tree size

andy.fingerhut16:05:05

I think typical default max JVM stack depth is on the order of a few thousand calls (it can be configured larger when you start the JVM via command line options). If you expect the DFS to traverse more nodes than that at maximum depth, then you should consider whether you might want to use a method that manages the DFS control flow via a data structure, vs. consuming call stack.

Erik B Good20:05:01

Thank you folks

noisesmith16:05:42

by simple non-optimized self call

noisesmith16:05:16

also your lazy structure via putting the self call inside concat already prevents stack usage explosion I think?

Karo18:05:56

hi team, I am facing this issue during deployment after changing java10 to 11 version. I also added JAXB library but the issue is still there. any suggestion? can this be related to the leiningen version as well?

hiredman18:05:52

you find yourself in a twisty maze of passages, all alike

👌 3
hiredman18:05:24

you are building an uberjar, and if you find the file named module-info.java in the uberjar and remove it, then maybe that will fix things

hiredman18:05:52

not 100%, but my guess is some dependency you are pulling in includes some java module stuff, and that is all be blindly shoveled into the uberjar by lein, so then java now thinks your uberjar is a java module thing, which makes things weird

Charlie Mead20:05:03

For anyone who’s perused https://www.manning.com/books/the-joy-of-clojure-second-edition?gclid=CjwKCAjwy42FBhB2EiwAJY0yQlgELvVtu7ErEKP-G0m9lqeWm4wS0BInny8yz1tHUxVM4gALeSBVohoCXVIQAvD_BwE, I was re-reading a section this morning and hit a point of confusion. In 8.1.1, there’s a quick review of syntax-quote, unquote, and splicing, with the example function contextual-eval:

(defn contextual-eval [ctx expr]
  (eval
   `(let [~@(mapcat (fn [[k v]] [k `'~v]) ctx)]
      ~expr)))
The author then writes: > The bindings created use the interesting '~s` pattern to garner the value of the built bindings at runtime. I don’t understand why '~s` is necessary/what problem it solves here. It seems redundant to me. When I attempted to write my own contextual-eval, I ended up with:
(defn contextual-eval [ctx exp]
    (eval `(let [~@(mapcat (fn [[k v]] [k v]) ctx)]
       ~exp)))
And it looks like the examples from the book evaluate to the same thing using my version.
(contextual-eval '{a 1 b 2} '(+ a b)) ; 3
(contextual-eval '{a 1 b 2} '(let [b 1000] (+ a b))) ; 1001

phronmophobic20:05:54

I'm not sure I would write it the same as their example, but there's a difference in behavior. Eg:

(defn contextual-eval [ctx expr]
  (eval
   `(let [~@(mapcat (fn [[k v]] [k `'~v]) ctx)]
      ~expr)))


(defn contextual-eval-mead [ctx exp]
    (eval `(let [~@(mapcat (fn [[k v]] [k v]) ctx)]
             ~exp)))

(contextual-eval '{a (+ 0 1) b 2} '(+ a b)) ;; exception
(contextual-eval-mead '{a (+ 0 1) b 2} '(+ a b)) ;; 3
Notice that they quote the value in their implementation

phronmophobic20:05:56

I haven't read the book and maybe it makes sense for their example, but calling eval in a macro seems super weird to me

Charlie Mead20:05:42

Oh it’s not a macro yet — just a regular fn being used to introduce the concept of macros

phronmophobic20:05:17

ohhh, right. duh. nvmd homerdisappear

Charlie Mead20:05:05

Thanks for your response earlier, walking through it now

phronmophobic20:05:06

it's a little easier to see what's going on if you just show the code being passed to eval:

(defn contextual-eval-form [ctx expr]
  (let [form `(let [~@(mapcat (fn [[k v]] [k `'~v]) ctx)]
                ~expr)]
    form))

(defn contextual-eval-mead-form [ctx exp]
  (let [form `(let [~@(mapcat (fn [[k v]] [k v]) ctx)]
                ~exp)]
    form))

(contextual-eval-form '{a (+ 0 1) b 2} '(+ a b))
;; (clojure.core/let [a '(+ 0 1) b '2] (+ a b))

(contextual-eval-mead-form '{a (+ 0 1) b 2} '(+ a b))
;; (clojure.core/let [a (+ 0 1) b 2] (+ a b))
Their's has a ' before (+ 0 1)

👍 3
Charlie Mead20:05:28

It makes sense why the book version breaks, but I’m still confused as to why it might be the preferred/idiomatic version vs. the simpler one. Trying to think of an example where contextual-eval-mead would be worse…

phronmophobic20:05:27

well, for the purposes of introducing macros, their version is closer to how a macro works

phronmophobic20:05:14

a macro gets unevaluated forms which is what their version is doing

👍 3
Charlie Mead20:05:27

ah ok. maybe that’s the use of having it in there then.

dvingo20:05:27

I recently came across a use of this in the malli codebase: https://github.com/metosin/malli/blob/master/src/malli/core.cljc#L1969

phronmophobic20:05:32

I usually would write that as:

(list 'quote (symbol (str name)))
it's a little more verbose, but it's easier for my brain to follow

👍 3
dvingo21:05:37

interesting, it's good to have the comparison - thanks!

phronmophobic21:05:30

the '~` idiom is a neat trick though. Might be worth getting used to.

Richie00:05:12

Why did the book use '` instead of just ` ? Their two examples give the same results. I don't understand.

hiredman00:05:30

They are not strictly speaking

hiredman00:05:57

A less opaque version of what the book is doing would be to replace `'~v with (list 'quote v)

Richie00:05:20

Why quote it though? Why not just use ~` ?

hiredman00:05:22

`~x is just x

hiredman00:05:07

`'~x is the value of x but quoted

Richie00:05:52

Oh, right.

hiredman00:05:41

In that particular case, I would never write it like that, but using '~ to embed quoted values in a syntax quote form is very useful

Richie01:05:30

Thanks. I think now I understand what's going on in this code even if I don't understand what they're trying to get across. I'll keep reading.

Richie01:05:36

Thanks again!

lunik122:05:16

I'm working my way through The Reasoned Schemer using core.logic and I'm having some trouble with the nevero goal defined in Chapter 6 frame 14:

(defrel (nevero)
  (nevero))
This should be a goal that never succeeds and never fails. My naïve translation to Clojure is
(defn nevero [] (nevero))
According to the book, Chap. 6 frame 17
(run 1 q
  #u
  (nevero))
should produce (), as #u fails before nevero is encountered, but
(run 1 [q] u# (nevero))
does not terminate for me (well, it does when it blows the stack). Does core.logic behave differently here or have I goofed something up?

lunik100:05:45

Ah I think I have it, should have been

(defne nevero [] ([] (nevero)))

lunik123:05:39

..but thinking about it I'm still not entirely sure why

Ethan Plante22:05:31

Hello! I'm 9 days into learning Clojure. How do you all approach solving problems?

phronmophobic22:05:20

For an in-depth look at problem solving techniques, G. Polya's "How to Solve It" is great, http://www.math.utah.edu/~pa/math/polya.html

phronmophobic22:05:36

The overview is a good starting point: 1. Understanding the problem 2. Devising a plan 3. Carrying out the plan 4. Looking back (eg. testing)

Juλian (he/him)22:05:52

small question about this and keywords in general: if I nest the IDs, like :example {:foo {:bar "foobar"}} in the dict for tr, according to the docs, I should refer it using :example.foo/bar - my first thought would have been :example/foo/bar and it works, as does :example.foo.bar , but those two are illegal keywords according to https://clojure.org/reference/reader - is there a way to transform a keyword by adding prefix/suffix? like in this example, how can i get from :foo to the :example.foo/bar ?

orpheus23:05:15

I have two files/namespaces that alias two different namespaces with the same symbol and I’m getting an alias conflict error. Why can’t I use the same-symbol (`:as some-symbol` ) when referring to two different namespaces in two different files?

// one.clj
(ns com.app.one
  (:require [com.app.models.one :as model]))

// two.clj
(ns com.app.two
  (:require [com.app.models.two :as model]))

phronmophobic23:05:15

looks ok to me. Maybe it's some other issue. What's the error you're getting? What's the symbol alias you're trying to use?

orpheus23:05:39

1. Caused by java.lang.IllegalStateException
   Alias model already exists in namespace
   neuromancer.core.pay.invoice, aliasing
   neuromancer.models.pay.account
I’m using :as model in both *.pay.invoice and *.pay.account

orpheus23:05:24

If I change the alias for one of them, it compiles w/o error

phronmophobic23:05:43

is there another require in the same namespace using model?

phronmophobic23:05:02

what do you mean by compile?

orpheus23:05:39

using cider’s hot key C-c c-k to compile the current namespace

orpheus23:05:16

and no I just have 3 requires in this namespace, only one aliasing :as model

phronmophobic23:05:20

ah, I'm guessing there's some persistent state which won't let you change it

phronmophobic23:05:44

you can either restart your repl or run

(ns-unalias *ns* 'model)

phronmophobic23:05:56

for each namespace before trying again

orpheus23:05:01

Cool let me give that a shot

phronmophobic23:05:01

there's probably a better way to reload each namespace without restarting the repl, but my repl workflow is fairly basic

orpheus23:05:49

Yeah there’s something weird going on with my repl. I restarted it, and it stopped giving me that error, but now It’s giving some other error that also looks like a caching problem

orpheus23:05:57

Ah nah figured it out, now everything’s compiling! just needed to restart the repl, thank you 🙂

🎉 3
didibus01:05:38

Most likely you have evaled one in a namespace already and so now there was already an alias in that namespace.

2