Fork me on GitHub
#clojure
<
2022-07-31
>
pinkfrog03:07:31

How can I define a var with its meta data coming from a variable? For example, I’d like to define a var foo, whose meta data is the meta data of another var.

Martin Půda04:07:45

(-> (def var1 value)
    (reset-meta! (meta #'var2)))

👍 1
Patrix05:07:10

I’d like some suggestions and pointers about handling currency and floating point. Mostly USD. (problem description follows in reply to this thread)

Patrix05:07:14

So I have some monetary values, that then are processed through a pipeline of sums, percentages, multiplications, divisions, averages, etc. While a lot of it could be optimized I’m sure, I’m basically transforming an Excel sheet into a small program. There are quite a few variables to handle, not just one value that is being threaded, and I not only need to keep track of the final result but a lot of intermediary values as well. Since it’s complex enough, I’m also making a test suite to make sure the end results are accurate, and especially here, floats come short because 23.4 and 23.3999999999999 are not equal, leading to test failures (as a singular example). As a simplified example:

I have:
monetary value X
modifier percentage M1
modified monetary value Y = X times M1
modifier percentage M2
list of frequency percentages F, made up of [F1 F2 F3 F4…Fn]
Threshold T

I want to calculate (again, simplified, but just to show there are a lot of steps involved)
Ys = list obtained by multiplying Y by each of the elements of F
Ya = sum of Ys divided by modifier M2
final result R = if Ya < T then Ya otherwise T
I tried bigdecs, which seemed to work well enough until I started some big divisions and got some ArithmeticExceptions, leading me to learn about with-precision. Caveat here is I don’t have enough knowledge in advance of the kind of values I’m expecting to know how much precision is needed, so again this is yielding results that might be inaccurate, and don’t pass the tests. I'm sure this comes up a lot and doing some research but haven't found anything conclusive, yet. I’ve found the clojurewerkz.money library and it might work, might not, would need to play around more before I can decide conclusively, but if I can avoid bringing in extra dependencies and run my little program in babashka, that’s good. If not well I’ll just have to live with it. Or am I going about this the wrong way?

p-himik05:07:20

Perhaps useful, but haven't used it myself: https://javamoney.github.io/

👍 1
Patrix05:07:13

Right, seems very similar to clojurewerkz/money. Seems in either case I have to re-think/re-write some of my code to properly support either one of those libs/formats, and hope it ends up with the correct results... gonna experiment

Patrix06:07:48

hmm also having trouble using org.javamoney/moneta {:mvn/version "1.4.2"} in my deps.edn ¯\(ツ)/¯ either way looks like I need to rethink my life my algorithm...

Patrix10:07:33

Well ok either one of those seem like they'll work. Guess I needed to convince myself to stop depending on unreliable float arithmetic and use libs that can handle currencies properly. Thanks!!!

👍 1
Ed13:07:49

You will have to do some rounding at some point, but you can use (with-precision 0 ,,,) to mean "unlimited" precision.

Ed13:07:01

but yes, you absolutely will get the wrong answer if you use floating point numbers

Patrix14:07:11

huh why did I not think of (with-precision 0...) thanks @U0P0TMEFJ, @U2FRKM4TW! For now the money related libraries seem to work, with proper rounding to half-up. Will keep testing

qqq06:07:22

What is the best "Clojure IDE + Repl" that runs fully in the browser, without support from an external server ?

borkdude08:07:07

Not exactly an IDE but more a tool that could be used to build such a thing: https://nextjournal.github.io/clojure-mode/

rolt10:07:10

the only thing that comes to mind is http://app.klipse.tech/

borkdude10:07:10

This one is also not an IDE but it is a REPL: https://babashka.org/xterm-sci/

borkdude10:07:04

Then we have e.g. https://4clojure.oxal.org/ which allows you to evaluate code, but to solve puzzles. Still not an IDE, but might give you some IDEas.

borkdude10:07:14

(all client side)

Alexander Bird13:07:08

Depending on what your goal is, this might help: I've become addicted to using Calva's "jack-in" REPL for VSCode. I will type one clojure expression in the file, then hit "alt-enter", and it immediately evaluates in vscode. I don't think that's what you're looking for, but if you are just after a certain unified dev experience, then that might help. However, you can run VSCode directly online: https://vscode.dev/. The Calva plugin isn't available in the online version, but maybe something else can work there and/or be created for it :man-shrugging:

NickName13:07:59

I’m using next.jdbc and wondering how to use transactions for the following testing logic. Say, I have this fixture: (use-fixtures :each (jdbc/with-transaction [t-conn *db* {:rollback-only true}] (fn [f] (do-some-insertions) (f)))) Inside f I have an api handler (from another file) which queries db and obviously does not have access to this transactional connection so it does not see any insertions. It would be too cumbersome to pass it manually. It is possible to drop this transaction and just do deletions when f is finished but it seems like a less “clean” way. Oh, and I’m using HugSQL for all the querying stuff. What would be the best way to proceed here?

rolt08:08:37

it really depends on your codebase, there are several ways to inject the connection object so that f is aware of it. The simpliest and probably dirtiest: have a dynamic *conn* var that you rebind in tests. I tend to not care about it and just delete the test db only once at the end of the tests.

NickName11:08:46

Hmm I’m just learning how web dev is done with clojure and probably worrying too much about doing things “the right way”. I guess just deleting everything after I’m done with it will be fine. Thanks for guidance @U02F0C62TC1!

Max21:08:08

Where I work we just truncate/delete all the rows in the db at the end of the fixture. It’s not quite as fast as doing the transaction trick, but it’s functional enough.

👍 1
stephenmhopper13:07:11

I find myself commonly extracting things from let bindings as top level defs when I’m debugging a function or hacking away in the REPL. I was hoping to make a basic macro to make this a bit quicker. My goal is to just print out or return the bindings so I can copy / paste them into whatever file I’m working in and then continue working on them from there. So far, I have this:

(defmacro extract-let [let-binding]
  (->> let-binding
    (second)
    (partition-all 2)
    (mapv (fn [[k v]]
            (println (str "(def " k " " v ")"))))))

(extract-let
  (let [x 5
        y (+ x 7)]
    (+ x y)))

Prints:
(def x 5)
(def y (+ x 7))
This should work for my use case. However, I was trying to get the macro to just return a vector of all of those defs instead. How do I do that without actually evaluating those defs? I kept messing with quoting / unquoting and couldn’t get it working just right.

Alexander Bird13:07:29

I have the same debugging needs too. My solution might not work for you, but because I use Calva VSCode plugin, I just type #break inside the let statement, then hit "alt-enter" (jack-in repl) to evaluate the let, and then it breaks there in vscode and I can see evaluate any variable or expression from there.

zimablue13:07:01

I've considered writing this macro before and never gotten around to it, there's a relevant tweet that i can't find which defines a scale from (println) (inline def) (tap) (portal)

Alexander Bird14:07:04

(defmacro extract-let [let-binding]
  `(do
     ~@(->> let-binding
            (second)
            (partition-all 2)
            (map (fn [[k v]]
                   `(def ~k ~v))))
     ~@(drop 2 let-binding)))
That should define every "let" variable as a ns-top-level variable, and then continue evaluating the the inside expression as well

Alexander Bird14:07:52

Or honestly, this might be easier to understand and use

(defmacro let-as-def [vars & logic]
  `(do
     ~@(for [[k v] (partition-all 2 vars)]
         `(def ~k ~v))
     ~@logic))
Then you would just retype your let into let-as-def Example:
(do
  (def x 100)
  (println "x is now" x)
  (let-as-def [x 5
               y (+ x 7)]
              (println "this" (+ x y))
              (println "that" (+ y y)))
  (println "x is now" x))
would print > x is now 100 > this 17 > that 24 > x is now 5

pppaul16:07:39

it may be an idea to have the macro walk over the body of a defn, and do this to all nested lets so to get the behaviour you change your defn to something like defn! or whatever to add the debugging features

stephenmhopper16:07:37

There’s some interesting responses in here. For my particular use case, I want this:

(extract-let
  (let [x 5
        y (+ x 7)]
    (+ x y)))
To return this:
[(def x 5)
(def y (+ x 7))]
I don’t actually want to evaluate any of the forms in the let binding

stephenmhopper16:07:27

For now, just printing is fine, but I’d like to improve my macro skills and was curious if what I’m hoping to achieve is possible

jpmonettas22:07:39

FlowStorm debugger can help with debugging this kind of stuff without having to write anything. For def functionality demo check: https://youtu.be/cnLwRzxrKDk?t=103 Repo : https://github.com/jpmonettas/flow-storm-debugger/

Alexander Bird02:08:08

I believe this will solve those specific requirements

(defmacro extract-let [let-binding]
  `(vector
    ~@(->> let-binding
           (second)
           (partition-all 2)
           (mapv (fn [[k v]]
                   `(quote (def ~k ~v)))))))
I do think this is a great exercise, even if I always prefer using breakpoints and remote debugging -- because it forced me to figure out macros better too!

👏 1
stephenmhopper03:08:28

Oh hey, that’s exactly what I was looking for. Thank you! Now I just need to figure out why the other versions of this macro I wrote didn’t work

sheluchin19:07:51

Could someone explain how the definition of defn works? I'm looking at it https://github.com/clojure/clojure/blob/5ffe3833508495ca7c635d47ad7a1c8b820eab76/src/clj/clojure/core.clj#L285 and it just looks very strange to me. I don't need to understand every implementation detail there, more so just the big picture of why it's structured as it is.

p-himik19:07:27

The whole binding part of the let is just to support the defn's wide range of possible arguments.

p-himik19:07:02

Plus :inline and metadata, but those are minor things.

p-himik19:07:25

Not sure what else to add without actually describing every form in the binding, unless you have specific questions.

sheluchin19:07:41

Starting from the top, it's: • interning a var called defn with the some metadata • the var's value is defined using the shorthand function macro fn, and it's then given the name defn • the :argslist in the metadata has a very different list than the nested defn fn - why?

p-himik19:07:25

The difference between the :argslist and the actual signature is due to two facts: • defn is a macro: (. (var defn) (setMacro)). That's where &form and &env come from • It has lots of optional positional arguments - that's why & fdecl is needed, along with all that value "pigeonholing" in the let.

sheluchin19:07:37

What is fdecl?

Alex Miller (Clojure team)19:07:41

just generally, because defn is defined before most of clojure.core exists, it can't use things you can normally use (like defmacro - that's why the setMacro)

sheluchin19:07:22

Yes, thank you @U064X3EF3, I understand it's bootstrapping from the JVM and can't leverage much Clojure at this point.

Alex Miller (Clojure team)19:07:48

fdecl is ultimately the the function declaration body that gets put in an fn

sheluchin19:07:11

Ah, "function declaration", of course.

Alex Miller (Clojure team)19:07:12

defn is really (def a (fn ...))

p-himik19:07:03

> What is fdecl? Given that it's preceded by &, it's a list of arguments that you pass to defn. So if you use (defn x "s" []), fdecl will end up being '("s" []). x is not there because it's bound to the name argument.

Alex Miller (Clojure team)19:07:09

the let parses out the initial parts like docstring etc and reduces fdecl to just the function part

sheluchin20:07:44

It makes sense from a high level. Would you guys say it's worth investing the time to learn internals like this? Does becoming more familiar with these areas provide much added understanding when implementing stuff in Clojure?

devn20:07:41

it kind of depends on what you're interested in. a lot of people will tell you that core is not idiomatic clojure, and that's often true, but I read core.clj anyway and don't really see the harm as long as you understand that up front

Alex Miller (Clojure team)20:07:09

I think the early parts of clojure.core can be a bit weird and are probably sufficiently different from "normal" clojure that maybe not useful

devn20:07:20

will it make you a better clojure programmer? I don't know… maybe?

Alex Miller (Clojure team)20:07:16

I think reading the source for other functions in core can be interesting and useful, just maybe not the defstuff so much

devn20:07:18

if your goal is to write idiomatic code, libraries are usually the better place to look. I like internals so I'm partial to reading core source, but ymmv

devn20:07:37

I also kind of liked learning early on how clojure core is built up

sheluchin20:07:30

It's certainly interesting, but I'd like to first learn the things that will give me the biggest gains. I'm getting more familiar with using the core functionality and now I'm just wondering what should be next on the list.

sheluchin20:07:35

Something like the content from https://insideclojure.org/2016/03/16/collections/ seems like a good next as well.

Stel Abrego21:07:37

I'm wondering what's the idiomatic solution to comparing two directories to determine if they have the same file structure and content. Off the top of my head I'm thinking I could use file-seq make a map of relative-path -> absolute-path for both directories and then make sure the key sets match and then iterate through the key-value pairs and check if the file contents match. :thinking_face: Seems like a common problem but I haven't found a Clojure implementation yet.

pppaul22:07:36

maybe you could use the diff command line

diff -qr directory-1/ directory-2/ 

hiredman22:07:53

Depends if you want a diff, or just want a finger print of a directory to compare

pppaul22:07:29

if you want to do everything in clojure, there is a lib call pallet that has custom merge functions, where you can dispatch on dir/file, but you have to do a little bit of work to tell pallet what those are before you merge

Stel Abrego22:07:49

It's for a Clojure unit test, so I don't want to use shell utils. I don't need a full diff, just a yes/no answer.

pppaul22:07:39

you can sha all the files, and create a non-nested map, and then do a compare. map key = file path, val = sha

1
hiredman22:07:57

If you just want a fingerprint I would do a sort of hash tree. Hash each file, then a directory hash is a hash of a list of the files in a directory and their hash

hiredman22:07:41

https://git-scm.com/book/id/v2/Git-Internals-Git-Objects has some examples of git tree objects which are used like this

Stel Abrego22:07:57

@U0NCTKEV8 Oh that's an interesting solution. I suppose that could be faster than directly comparing file content when there are lots of large files.

hiredman22:07:08

There are also libraries for hashing clojure data structures (Puget maybe?), so you could just build a data representation of the filesystem then hash that

Stel Abrego22:07:37

I'll play around with that, thanks!

Stel Abrego23:07:23

Hey @U0NCTKEV8 wrt to taking the hashing approach, is there any reason why I can't just have a :dir value for each directory key? Since all the normal file entries have to match, seems like I don't have to have a SHA for the directories. :thinking_face:

Stel Abrego00:08:33

To clarify with fake SHAs:

{"index.html" "3lkj423lk4j3kj4",
 "about.html" "lkfjjkl3j4kjk3jj5",
 "blog" :dir,
 "blog/cool-post.html" "3j5jlh544kh5"}
Wouldn't that be enough information to determine if the structure and content is identical to another directory?

Stel Abrego00:08:45

I suppose taking the SHA of directory contents provides some cool optimization paths for certain operations but I don't think I need any for my use case

hiredman00:08:45

It looks like you are still making a flat map of files to hash values

hiredman00:08:22

Which is not what I suggested, I suggested a tree hash or a Merkel hash

hiredman00:08:38

In which case using a :dir marker would not be sufficient because then the contents of a nested directory would not contribute to the hash of its parent

Stel Abrego00:08:45

Ok that makes sense I just don't think I need a recursive data structure in this case

Stel Abrego00:08:54

As in I don't need to compare nested dirs so I don't need a SHA for them

Stel Abrego01:08:08

Posting my solution in case anyone is interested. Not perfect but seems to work well enough for my use case which is unit tests (using clj-commons/digest lib):

(defn dir-contents-map
  "Create a datastructure representing a directory's structure and contents in
  order to compare it with another directory. Creates a map of: relative paths
  (string) -> md5 checksums (string) of all the files inside the directory.
  Nested directory paths are not checksummed and have a value of :dir"
  [dir]
  {:pre [(-> dir (java.io.File.) (.isDirectory))]}
  (let [files (-> dir (java.io.File.) file-seq)
        parent-path-name-count (-> files first (.toPath) (.getNameCount))]
    (reduce (fn [contents-map file]
              (let [abs-path (.toPath file)
                    path-name-count (.getNameCount abs-path)
                    rel-path (str (.subpath abs-path parent-path-name-count path-name-count))
                    md5-checksum (if (.isDirectory (.toFile abs-path))
                                   :dir
                                   (digest/md5 file))]
                (assoc contents-map rel-path md5-checksum)))
            (sorted-map) (rest files))))

(defn diff-dirs
  "Determine if two directories have the same file structure and content. If
  differences, return list of relative filenames that are different and log
  diffs. If identical, return nil"
  [dir1 dir2]
  (let [cm1 (dir-contents-map dir1)
        cm2 (dir-contents-map dir2)
        [d1 d2 _] (data/diff cm1 cm2)
        rel->abs-path (fn [parent-dir path] (str parent-dir "/" path))
        mismatches (-> (merge d1 d2) keys)]
    (doseq [mismatch mismatches]
      (log/warn "Found mismatch:" mismatch)
      (-> (safe-sh "diff" (rel->abs-path dir1 mismatch) (rel->abs-path dir2 mismatch))
          :out
          println))
    mismatches))

Noah Bogart23:07:29

I remember someone once saying it's an anti pattern to separate a project’s code into clj, cljs, cljc directories. If that's true, what’s the best way to comingle the various filetypes without stepping on each other's toes?

seancorfield00:08:02

Hmm, I don't think I've seen anyone suggest that (that it's an anti-pattern). I can see good reasons -- from a tooling p.o.v. -- to keep them separate, since it makes it easier to group .cljs and .cljc for FE tooling and .clj and .cljc for BE tooling. But I think it also depends on the project and why you need to separate code itself into those different extensions. For example, HoneySQL doesn't separate those files, but everything is .cljc except one test file, which is specific to some JVM/Clojure functionality. Expectations (the clojure.test version) also doesn't separate those files, but everything is .cljc except for one test file which is specific to ClojureScript.

👍 1
Noah Bogart01:08:57

Yeah, tooling is a major reason why I've not tried to merge them. In the codebase I'm thinking about, there's a very clear delineation between frontend and backend code with a small amount of shared code. There’s not been any reason to merge them yet, but that idea has been itching my brain for a couple years now. Glad to hear that you think it's fine to keep them separated.

Chase01:08:53

You might be remembering it from some convos I've seen on #shadow-cljs (if you search src/clj ). From the docs: https://shadow-cljs.github.io/docs/UsersGuide.html#source-paths I can't recall if it was just a personal preference thing of his but I switched to using frontend/ and backend/ directories after reading it in there.

seancorfield01:08:35

@U9J50BY4C What do you do about code shared between both ends?

Chase01:08:28

I haven't gotten that far. hahaha. Maybe I would throw it in a shared/ directory.

wevrem01:08:27

I also separate FE and BE (call the directories ‘app’ and ‘server’) and have a ‘shared’ for .cljc. I guess that sets me free to commingle code, but in practice it doesn't happen. So in the end I'm still separating the files by type, just without using the extensions as the folder names.

Drew Verlee02:08:04

@U9J50BY4C may i suggest the name "middlend" instead of "shared"?

3
😋 1
lread03:08:30

When I started working on rewrite-clj v1 I had separate dir structures but then realized the majority of my sources were becoming .cljc so the separate dirs were more noise (to me) than helpful and I turfed them.

👍 1
Noah Bogart03:08:27

@U9J50BY4C good find! That is the quote I had in mind.