Fork me on GitHub
#clojure
<
2022-01-03
>
Eddie03:01:36

If we destructure a map with defaults, is there any syntax sugar for getting the entire map with the defaults?

(let [{:keys [k] :or {k 1} :as m} {}]
  m)
;; Actual: {}
;; Desired: {:k 1}

Eddie03:01:02

I know we can simply merge like this

(let [{:keys [k] :or {k 1} :as m} {}]
  (merge m {:k k}))
but as the number of defaults grows it starts to look a bit redundant to me. Just curious if there is a way to avoid it.

dpsutton04:01:15

i'd probably make some kind of normalize function that takes the m and (merge defaults m) and then you are done

Eddie04:01:28

Is there a way to get the defaults as a their own map, or do I just have to construct that map manually?

Alex Miller (Clojure team)04:01:47

There is no way to do that, you should merge an explicit defaults map if you want that

Eddie04:01:35

Ok, thanks for confirming.

seancorfield05:01:09

@U7XR2PZFW What I tend to do is:

(let [:keys [k] :as m} (merge {:k 1} val)]
  m)
but this feels less satisfactory when the destructuring would otherwise be in a function's argument, since you lose information in doc for the signature unless you add in :arglists and then you're duplicating what's already in the code 😐

👍 1
Nom Nom Mousse10:01:39

I'm having trouble with error messages (fro exceptions) not showing up. I have gotten used to error messages not showing when they happen in a sub-process (if that is the correct term). But in this case, I do not think being in a sub-proces should be the issue. I have an application that makes use of multi-threading (starting babashka processes). I have a main method that starts a babashka process. The babashka process runs a bash process, and, upon completion, it updates an atom. This atom has a watcher. I have included the following code in the watcher function:

(println "in watcher")
-code-that-intentionally-throws-error
(println "done with watcher")
"done with watcher" never prints. The sequence of events is simple: 1. call babashka process 2. after babashka process finishes an atom is updated 3. the atom watcher is made to fail Since the atom watcher fails silently it seems to me that the watcher is still operating in the context of the babashka process. Am I correct in surmising this? If so, how do I fix it?

p-himik10:01:01

You write contradicting things: • "The babashka process runs a bash process, and, upon completion, it updates an atom" • "after babashka process finishes an atom is updated" So where exactly does that atom live, what updates it?

p-himik10:01:00

A few other things: • Threads and processes are completely different concepts (you mention multi-threading and right after you mention starting a process) • Atoms and their watches aren't shared/moved between processes in any way • "done with watcher" can never be printed regardless of the context if the form before it is just (throw ...) outside of any (try ... (catch ...))

Nom Nom Mousse10:01:03

The babahska process wraps a java process. This java process is updated with a callback function like so:

(defn add-completion-dispatch [java-proc myvals]
  (.thenApply
   (.onExit java-proc)
   (reify java.util.function.Function
     (apply [this p]
         (reset! myatom myvals)))))
So the callback function updates myatom. myatom lives in a different clojure file.

Nom Nom Mousse10:01:16

> "done with watcher" can never be printed True. But this shows that the call was interrupted by an exception without any exception message showing.

p-himik10:01:11

.thenApply returns another future. I'm pretty sure you'll find your exception there if you try to .get it.

Nom Nom Mousse10:01:21

> Threads and processes are completely different concepts Yes, in this case, I mean processes since they do not run in the same memory space.

Nom Nom Mousse10:01:58

> I'm pretty sure you'll find your exception there if you try to `.get` it. I really appreciate your help. Thank you!

👍 1
tami513:01:38

Hey everyone, I've been doing a lot of duct (sane integrant framework) lately and I can't help to wonder: "why such a great framework isn't heavily adopted by the community" to the point where libraries offer duct keys. It does so much in terms of development speed and state management, in addition to greatly decreasing the amount of repetitive code to say setup web api, integrate datomic database or have a pre-made pure module part of a running system. I'm using a sophisticated reitit duct-module that I'm currently under-development that really made setting up a production ready reitit ring app with a few lines of code and configuration key. I'm hoping to release that module soon to prove my point. Anyways, this question crossed my mind after seeing how little community activity is devoted to duct and how few stars it has on github and thought maybe I should share it here to create some type of awareness or get some feedback. Thanks

vemv13:01:15

libraries aren't offering Duct keys in the very same way they aren't offering Component or Integrant components, even when those are used by most webapp setups out there, regardless of framework/libs choice Most times making a Component is fairly trivial, although it would certainly be nice to have some readily-available components. I think the reason for this absence is simple: most commercial development happens behind closed doors, in a private monorepo, possibly with just-enough quality and no particular reusability for people outside a specific organisation.

vemv14:01:20

Other than that I'm not aware of any particular flaw from Duct's side and hear from time to time of great companies using it (and publishing compatible libs!). I think there's some favorable sentiment for web frameworks (despite the cliche among Clojurists), the next problem is which framework - i.e. why Duct in particular? A few other 'competitors' seem just as modular and capable. it's some sort of Lisp Curse perhaps - there's always one more alternative to choose from, or you can always make up your own alternative without much effort. Same for e.g. Malli vs. Spec perhaps, and various other examples

emccue15:01:56

> it’s some sort of Lisp Curse perhaps - there’s always one more alternative to choose from, or you can always make up your own alternative without much effort. Unlike the JS ecosystem though, I think for a lot of stuff there are few enough options to make a “choose your adventure” type thing

👍 1
Akiz15:01:47

The biggest problem is lack of a documentation IMHO. Luminus has got book Web Development with Clojure and enough articles. Duct is missing a single, consistent source of information and good examples - especially for beginners.

seancorfield18:01:38

FWIW, next.jdbc does provide support for Component out of the box (without having to depend on Component itself). I personally don't like Integrant and don't use it and therefore have no idea what it would take to "offer duct keys" @U02PHJ3C1QV?

seancorfield18:01:32

I like Component because it's very small and focused and simple (and it is as well-documented as it needs to be since it is so small and focused).

tami520:01:25

@U45T93RA6 Yes, making components in the original sense is trivial I agree, but duct has totally different approach, instead of components you get modules, or straightforward duct keys. The case with duct isn't to define a large record and ask the user to require it and start using, it's more like hey here's a configuration key you can initialize (or don't) with this set of parameters to produce a customized value that can be referenced in other "modules" or "keys" by passing it to it's arguments. When you use a particular library over and over, you tend to write the same code over and over with small tweaks depending on the library configurability and domain. It's great to have an abstraction over a "feature" or "module" that would only require you to write a key definition instead of a code, for example https://github.com/duct-framework/middleware.buddy. You don't have to reinvent the wheel every time u use buddy, you can just define the keys inside your config and pass it's value around. The true value of duct is in-making the system extremely easy to reason with. It definitely isn't just another way to write or perceive components but it's all about integration and making it easy to build complex relationships between keys or modules without having to write a single clojure code to glow things up, say middleware and a router. > Duct in particular? I understand, I always say having too much alternatives doing the same thing (and really good) with minor differences break the community. Duct in particular, because it's made by the author of integrant, it's matured greatly, and once you get it and work with it feels sane. > Lisp Curse 😆 Yes, but it doesn't have to be that way with clojure, the more easy and simple the ecosystem is the more great tooling and system get build on top it. Just imagine of clojure.core had variants and clojure allowed for core to be directly replaced by the authors and libraries are compueting to show which their clojure.core has more and better performant functions. It would completely break the community development, and new memebers will just have to not only pass the stage of learning lisp, but also, evaluating 1000 different ways to develop with clojure.

tami520:01:03

@UBRV1HXPD true ture, I honestly, didn't quite get the whole duct thing until like few months later, but really it as worth it. My suggestion if you someone want to get into duct is to read through the code base and tests of duct modules and duct.core. Oddly, it is straightforward.

seancorfield20:01:24

@U02PHJ3C1QV Isn't System meant to be a set of Duct modules?

seancorfield20:01:00

Hmm, I can't even find that repo now...

seancorfield20:01:10

Oh, haha, System is the Component equivalent of Duct I think. Never mind!

tami520:01:31

Hey @U04V70XH6, I've been using a lot of your work. Thank you ❤️ > next.jdbc does provide support for Component out of the box How so? you mean by how the code base is structured? edit: oh I see there are actual component key. nice > "offer duct keys" Meaning creating duct keys like next.jdbc/connection which accept the same db-spec through defmethod init-key :next.jdbc/connection. Here I inivited you on github to a reitit module I'm planning to release soon. It may make more sense or the value of duct may be better seen. feel free to DM with whatever.

seancorfield20:01:53

Yeah, I'm not interested in adding dependencies to next.jdbc to support such things -- I was able to add Component support because of metadata-based protocol extension, so I don't need the Component library in order to do that.

seancorfield20:01:02

You can't call Duct's defmethod without depending on Duct, right?

seancorfield21:01:07

You can have arbitrary IMeta objects that support the Component lifecycle without needing any dependency on Component itself.

🙌 1
tami521:01:16

> You can't call Duct's defmethod without depending on Duct, right? Not defmethod it an integrant thing, through a multimethod defined in integrant.core

tami521:01:26

> Yeah, I'm not interested in adding dependencies to next.jdbc to support such things -- I was able to add Component support because of metadata-based protocol extension, so I don't need the Component library in order to do that. Oh I see it makes sense. even worst for maintaining it

seancorfield21:01:48

Yeah, supporting something that requires registration via defmethod means n+1 libraries or else adding that something as a dependency to every library that wants to support it -- even if your users don't want/need it.

seancorfield21:01:22

Or having additional namespaces that must be required by users to pull those deps in -- which also complicates the build/test process since you need to ensure those nses are not referenced anywhere by default when you don't have that dependency. It's part of what I don't like about Integrant.

seancorfield21:01:03

System took that path: a large collection of independent nses with each one requiring a specific library, to hook it into Component. Back before metadata extension of protocols was a thing.

seancorfield21:01:36

But then maintenance of System is tied to all those other libraries... 😞

seancorfield21:01:26

(I declined the repo invite deliberately -- no need to reinvite me)

zendevil.eth18:01:10

what’s the best way to check whether a string is a numerical string in clojure?

zendevil.eth18:01:25

like “123”, or “342315" etc?

Alex Miller (Clojure team)18:01:43

In Clojure 1.11 you will soon be able to use parse-long but until then, calling Long/parseLong would be best

hiredman18:01:27

or (every? #(Character/isDigit %) "342315")

hiredman18:01:05

(every? returns true for empty inputs, isDigit returns true for unicode digits)

zendevil.eth18:01:23

let’s say I have a map: {"Leading zeros ids" "00121", "Test" "123", " test 1" "abc"} and I want to change every value that’s a number string to be a number except those that have leading zeros, how would I do that?

zendevil.eth18:01:46

do I have to convert to a vector of vectors and back or is there a better way?

zendevil.eth18:01:06

this is what I got: (into {} (map (fn [[k v]] [k (if #(and (every? (Character/isDigit v)) (not= (first v) "0")) (read-string v) v)]) {"Leading zeros ids" "00121", "Test" "123", " test 1" "abc"})) But it gives:

{"Leading zeros ids" 81, "Test" 123, " test 1" abc} 

hiredman18:01:59

use a regex to check all digits and doesn't start with 0, your not= is failing because the first of a string is character not a string

seancorfield18:01:23

or (str/starts-with? v "0")

hiredman18:01:47

a regex can do the digits check and 0 check at once

hiredman18:01:11

[1-9][0-9]*

seancorfield18:01:15

True, but I'm using 1.11 so I'd use parse-long 🙂

dpsutton18:01:24

you need to parse in base-10. read-string is dangerous and not up to the task for this (unless you explicitly are dealing with octal numbers)

hiredman18:01:29

parse-long wouldn't do the 0 check

seancorfield18:01:49

(and (string? v) (not (str/starts-with? v "0")) (parse-long v)) 🙂

hiredman18:01:08

yeah, if you are using Long/parseLong you can just force radix 10

hiredman18:01:29

if that is the only reason you are avoiding parsing numbers that start with 0

seancorfield18:01:56

That throws.

dev=> (Long/parseLong "abc")
Execution error (NumberFormatException) at java.lang.NumberFormatException/forInputString (NumberFormatException.java:67).
For input string: "abc"
dev=> (parse-long "abc")
nil

seancorfield18:01:06

(just a difference to be aware of)

zendevil.eth18:01:26

I don’t understand why it’s throwing since (every? (Character/isDigit v)) would be false in the case of “abc”

zendevil.eth18:01:43

(into {} (map (fn [[k v]] [k (if #(and (every? (Character/isDigit v)) (not (clojure.string/starts-with? v "0"))) (Long/parseLong v) v)]) {"Leading zeros ids" "00121", "Test" "123", " test 1" "abc"}))

hiredman18:01:18

if you are parsing anyway, it is best to avoid the digit check if you can since that is what parsing does anyway

hiredman18:01:46

my suggestion of using every? was based on you asking to check if a string contained digits

zendevil.eth18:01:00

no but I want to keep the numbers with leading zeros as strings

seancorfield18:01:17

You have (every? (Character/isDigit v)) instead of (every? (fn [v] (Character/isDigit v)) v)

seancorfield18:01:21

dev=> (every? (Character/isDigit "abc"))
Execution error (IllegalArgumentException) at dev/eval94029 (dev.clj:1).
No matching method isDigit found taking 1 args

zendevil.eth18:01:23

user=> (into {} (map (fn [[k v]] [k (if #(and (every? (fn [v] (Character/isDigit v)) v) (not (clojure.string/starts-with? v "0"))) (Long/parseLong v) v)]) {"Leading zeros ids" "00121", "Test" "123", " test 1" "abc"}))
Execution error (NumberFormatException) at java.lang.NumberFormatException/forInputString (NumberFormatException.java:65).
For input string: "abc"

seancorfield18:01:18

You have #() not being called.

hiredman18:01:21

given the difficulty you are having with syntax maybe we should move to #beginners (or not, it will likely be the same people helping either way)

seancorfield18:01:43

So you're asking if some-function ... which is always true since some-function is truth

zendevil.eth18:01:12

yes that fixed it

seancorfield18:01:41

If you have a compound expression, the way to debug it is to try each little piece in the REPL.

seancorfield18:01:18

That's what I did to work from the inside of your failing expression to the outside, identifying the bugs along the way.

zendevil.eth19:01:02

the Character/ thing doesn’t work in cljs

seancorfield19:01:49

Doesn't cljs already have parse-long?

seancorfield19:01:13

Or something similar. Since Long/parseLong won't work on cljs either.

lukasz19:01:25

window.parseInt

seancorfield19:01:41

@domagala.lukas No portable way to do it?

seancorfield19:01:22

cljs.reader/read-string I guess? and clojure.edn/read-string for clj

seancorfield19:01:46

(and a read conditional on require/refer one or the other)

Lukas Domagala19:01:54

i’m using

(defn- str->int [s]
  #?(:clj (Integer/parseInt s)
     :cljs (js/parseInt s)))
you could do the same thing for longs

1
seancorfield19:01:37

Hopefully, with parse-long etc in clj, we'll get same-named functions in cljs core as well "soon"?

Alex Miller (Clojure team)19:01:12

yes, my expectation is that the parse functions will get ported

1
zendevil.eth19:01:43

I mean Character/isDigit doesn’t work

seancorfield19:01:46

Kind of important to specify whether you're trying to solve a problem in clj or cljs (or both) since Long/parseLong isn't a thing in Clojure either -- both Character and Long are JVM classes.

dpsutton19:01:14

yes. these are other solutions you could use for cljs.

p-himik19:01:08

FWIW I would definitely move this to #beginners Regardless of whether it's the same people helping or not - after all, the sets of readers are different.

👍 2
zendevil.eth20:01:38

use-fixtures :once seems to work once in the context of one namespace. Is there a way for it to run once in the context of all tests in all namespaces?

Alex Miller (Clojure team)20:01:30

No, that's not a feature of clojure.test

seancorfield21:01:04

I seem to have run into that a number of times. We have our own wrapper for Cognitect's test-runner that does "per-suite" fixtures, and Polylith has added "per-project" fixtures (at my request) which is essentially "per-suite" since it runs the test suite for each project (and runs only tests that reflect code changes since the last "stable" point -- or whatever point of reference you want).

seancorfield21:01:21

We are liking that incremental testing "feature" 🙂

zendevil.eth21:01:46

we’re using use-fixtures :once to run db migrations in each namespace, but we only need to run the db migrations once before running all the tests. What’s the best way to do that?

seancorfield21:01:46

@U01F1TM2FD5 What are you using to run your tests?

seancorfield21:01:49

We have our DB migrations as a separate step in our build.clj script and run that prior to running tests -- so we have a build task that is "run DB migrations; run whatever tests you want"

zendevil.eth23:01:53

We're using lein test command

zendevil.eth23:01:24

How to do this with lein?

seancorfield23:01:52

I don't know if there's a way to do it with Leiningen -- I haven't used it for years.

seancorfield23:01:56

If you can run your DB migrations via a lein task, then you could specify :prep-tasks in the :test profile I guess. See if https://github.com/technomancy/leiningen/blob/master/doc/PROFILES.md#task-specific-profiles has anything that helps

seancorfield23:01:44

(Leiningen's rigidity was a big part of why we switched to Boot back in 2015 and then the Clojure CLI / deps.edn in 2018)

zendevil.eth16:01:40

I tried: :prep-tasks ["run -m user"] but that gives: 'run -m user' is not a task. See 'lein help'.

seancorfield17:01:45

@U01F1TM2FD5 I think you need to create an alias for the DB migration task, and then you can specify that alias in :prep-tasks. Those :prep-tasks strings need to be just task names or alias names -- not command-lines.

seancorfield05:01:30

I see Dan helped you in #leiningen and suggested :prep-tasks [["run" "-m" "user"]] -- did that work for you?

zendevil.eth08:01:20

it worked with the alias

1
Jacob Rosenzweig23:01:39

Is there an idiomatic way to do some side effect over a list? Would I just use the map function?

Ben Sless07:01:05

or doseq

👍 1