Fork me on GitHub
#clojure
<
2020-06-02
>
javahippie07:06:34

I am trying to use Testcontainers for my current projects’ integration tests, and I have a question about the form. If I use Java Interop and set up the fixtures myself, it looks like this:

noisesmith16:06:01

does GenericContainer have side effects on construction?

javahippie16:06:46

I believe not on Construction. It only interacts with docker upon .start and .end

javahippie16:06:32

Of course the builder pattern used by the lib manipulates the instance with every call

javahippie07:06:29

I figured this is a lot of boilerplate code, so I thought about creating a macro which creates the def with the provided label, sets up the fixture and wraps the testcontainer object so its properties can be accessed with keywords. Maybe it will allow overriding the use-fixtures method, too, for more flexibility. I don’t have much experience with Macros, so what do you think of the idea? Will this come back and bite me after some time? Is there a more idiomatic way to solve this? Or should I just write a small testcontainers wrapper and keep the definition and the fixture separate for more readability?

kimim07:06:15

dear all, how to put a vector of maps to a single map, with each key as the key in maps and value as a vector of values? Thanks!

kimim07:06:30

something like this: [{:name "A" :age 11} {:mame "B" :age 12}] => {:name ["A" "B"] :age [11 12]}

raspasov07:06:50

(merge-with
  (fn [v1 v2]
    (let [v1' (if (vector? v1) v1 [v1])]
      (conj v1' v2)))
  {:name "A" :age 11}
  {:name "B" :age 12}
  {:name "C" :age 13})
=> {:name [“A” “B” “C”], :age [11 12 13]}

p-himik07:06:03

It will not work if one of the existing values is a vector already.

raspasov07:06:52

Well… 🙂 we assumed they are strings initially… if you have such irregular data, that’s a problem in itself I would say… I would make sure my inputs are consistent (if I have control over it)

raspasov07:06:31

If they are all vectors to start with, it’s somewhat simpler

raspasov07:06:39

(apply
  merge-with
  into
  [{:name ["A"] :age [11]}
   {:name ["B"] :age [12]}
   {:name ["C"] :age [13]}])
=> {:name [“A” “B” “C”], :age [11 12 13]}

p-himik08:06:02

A generic solution based on the code of merge-with:

(defn merge-into-vecs [maps]
  (when (some identity maps)
    (let [merge-entry (fn [m e]
                        (let [k (key e) v (val e)]
                          (update m k (fnil conj []) v)))
          merge2 (fn [m1 m2]
                   (reduce merge-entry m1 (seq m2)))]
      (reduce merge2 {} maps))))

kimim08:06:12

Thank you very much. That's great.

p-himik09:06:57

Clojure doesn't have an analog to Java's ==, does it?

p-himik09:06:10

Right, thanks! Completely blanked out on that one.

andy.fingerhut16:06:04

The valid use cases for identical? are pretty unusual in Clojure, since = is often what you want. But I won't belabor the point if you know you need identical? for some reason.

sdledesma10:06:57

Is there a preferred way to create a map from a seq of key-value pairs? I use both (reduce conj {} collection) and (into {} collection) but I'd like to settle on whatever is more popular. I checked the clojure style guide but haven't found an entry on the subject.

joelsanchez10:06:54

pretty sure everyone uses into {}

sdledesma11:06:37

now that I see the question on the screen and out of my head it all makes sense. I feel a bit silly now 🙂

Mohammed13:06:51

Hi, I am trying to create a map of ngram frequencies from a decent text file. The code I wrote emits an OutOfMemory. While the same thibg can be easily done with Python and C++ code, with less than 20% of the memory that Clojure needs. Following I will post parts of the code. Can anyone tell me how to get better on memory use?

hindol13:06:52

Clojure uses more memory by design, but not that much more. I can go over the code if you post it.

Mohammed10:06:40

Thank you very much for your reply. I have posted the code the uestion already. Here it is again.

Mohammed13:06:55

Hi, I am trying to create a map of ngram frequencies from a decent text file. The code I wrote emits an OutOfMemory. While the same thibg can be easily done with Python and C++ code, with less than 20% of the memory that Clojure needs. Attached is my code. Could anyone, please, tell me how to get better on memory use?

chrisblom15:06:50

I don't see anything obviously wrong here. One thing that might help is skipping the joining of the ngrams

chrisblom15:06:03

that should save some allocation of new strings

chrisblom15:06:17

(defn ngrams  [n N sent]
  (let [s (str/split sent #"\s+")]
    (for [i (range n (inc N))]
      (partition i 1 s))))

chrisblom15:06:41

in ng-count (map ...) (apply map concat) can be replaced with (mapcat ...)

chrisblom15:06:06

(defn ng-count [n N filename]
  (with-open [rdr (io/reader filename)]
    (->>(line-seq rdr)
        (mapcat (partial ngrams n N ) )
        (map frequencies )
        doall)))

chrisblom15:06:30

Probably does not matter much for perf. or mem usages though

chrisblom15:06:49

How many ngrams do you expect in the text file, and what is your -Xmx setting?

andy.fingerhut16:06:18

Every JVM string object has an overhead of about 40 bytes, regardless of its length, so representing N-grams as vectors of N strings, rather than creating a new string, might save memory. There is still some memory allocated for each vector object, too, so those two approaches might end up near the same memory, after all.

Mohammed19:06:57

Thank you very much for your replies, Chrisblom and Andy. That is very helpful. I actually tried many values for -Xmx from 1000 to 2000.

Mohammed19:06:06

I noticed something else, which I have no clear information of. I noticed that the program runs in multithreaded manner. Are the threads created for the processing executed by the program or they are rather Java's utilities to handle internal tasks?

andy.fingerhut19:06:12

There are multiple different garbage collection methods that modern JVMs implement. Some of them use separate threads, and can take advantage of separate CPU cores from those that execute your main code, to perform garbage collection.

andy.fingerhut19:06:01

Determining whether that is what is happening in your case is possible, with appropriate examination of the JVM's operation, e.g. using some kind of JVM profiler tool that can attach to a running JVM.

andy.fingerhut19:06:58

You can also type Ctrl-\ in a terminal from which the JVM was started to cause it to dump out info about all threads, maybe more detailed than you would like, which might help you discover what the threads are called, at least, and a snapshot of what their current stack trace is for the code they are executing.

Mohammed19:06:44

Perfect, great info... many many thanks

pithyless16:06:32

@U015D447PSL took a stab at your question and only now realized that Slack showed me a 7 day old thread. 🙂 Anywho, it may still be relevant - I think you can save a lot on memory, by not creating so many temporary thunks and using transducers. Here's a gist as an example. Probably some things could be even more elegantly described, but hopefully this gives you some ideas: https://gist.github.com/pithyless/44ce8908cee196fd3f5ace685b8db2f4

plins13:06:12

is there a more idiomatic way of writing this (if (empty? coll) [1] coll)?

delaguardo13:06:01

(or (seq coll) [1])

❤️ 4
delaguardo13:06:31

not sure is it more idiomatic or not, but that is the form that I’m using a lot

plins13:06:59

I was trying to go for an or but I was missing the seq function

plins13:06:08

thank you 😃

p-himik15:06:20

Be careful though if there are type checks.

=> (vector? (seq []))
false

☝️ 8
dominicm16:06:10

Needing this function might indicate you are doing https://stuartsierra.com/2015/06/10/clojure-donts-heisenparameter

👍 4
wombawomba17:06:15

I’m trying to define a function in a macro, where the function name depends on one of the values that gets passed to it:

(defmacro foo [m] `(defn ~(symbol (:k m)) [])

(let [arg {:k "foo"}] (foo arg)) ; => IllegalArgumentException
How can I get this to work?

seancorfield17:06:18

@wombawomba macros are passed their arguments unevaluated, so foo is invoked with the literal symbol arg in that case.

wombawomba17:06:00

Right. Is there a way to evaluate it before defining the function?

seancorfield17:06:43

Macro expansion happens before code evaluation. The let hasn't been evaluated when foo is macroexpanded.

phronmophobic17:06:01

you can do this without a macro:

(defn define-function [m]
  (intern *ns* (symbol (:k m)) (fn [])))
(let [arg {:k "foo"}] (define-function arg))

wombawomba17:06:05

Okay yeah that makes sense. Are you saying I should be using a regular function here?

wombawomba17:06:13

@smith.adriane thanks! What if I want to use schema.core/defn?

phronmophobic17:06:12

using define-function as above probably isn’t a good idea. do you have little more information about what you’re trying to do?

wombawomba17:06:41

I'm trying to autogenerate a bunch of functions from a data structure

phronmophobic17:06:11

is there a reason the functions need to be interned into the namespace?

wombawomba17:06:37

Well, I want them to act as regular functions in a particular namespace

noisesmith17:06:12

what's the benefit of putting them in the namespace?

noisesmith17:06:28

I mean, if the names are generated like that, how does the caller even know what they are

noisesmith17:06:00

often the solution is to define a top level hash-map, and put the functions in there

wombawomba17:06:48

Wouldn't knowing what the keys are in the hash map cause the same problem?

noisesmith17:06:14

you can iterate hash-maps, you can ask for the keys, etc. - they are meant to be used as data

wombawomba17:06:24

Either way, I wouldn't worry about the caller :) I'm more concerned about getting this to work

noisesmith17:06:34

treating a namespace that way is clumsy, less efficient, and more brittle

noisesmith17:06:19

data-structure-like operations are added to namespaces to make them more useful / extensible, but they still aren't as useful or extensible as data structures are

wombawomba17:06:59

Yeah got it, although I still think doing it this way is appropriate for me.

wombawomba17:06:17

I mean, it's not really that clear-cut – after all, what I'm doing here is almost the same thing as what defn does, only one level up

wombawomba17:06:10

And surely there's a reason that people prefer to use defn over a top-level hashmap of fns

phronmophobic18:06:13

if you really wanted to intern a variable, you can probably use schema’s schematize-fn

phronmophobic18:06:28

it’s a lower level api that schema.core/defn relies on

phronmophobic18:06:27

ymmv, but based off your use case description so far, it does seem like you’d want to use something like a top-level hashmap of some sort

wombawomba18:06:55

Well, the main thing I'm after is the regular compile-time guarantees of the function calls

wombawomba18:06:08

As well as the regular documentation etc with codox et al

wombawomba18:06:18

Is there a way to get those with a hash map?

wombawomba18:06:41

I'll look into schematize-fn, thanks :)

phronmophobic18:06:26

there are ways to have a toplevel hashmap work with docs and compile-time checks. do you have an example of the hashmap you’d be using to generate code from?

noisesmith18:06:12

the compile guarantees yes, you won't get compatibility with eg. codox without adding :doc metadata (and I'm not for sure that always works)

wombawomba18:06:15

Not really, although if you pointed me in the right direction I would be able to figure it out

wombawomba18:06:41

For the compile-time guarantees, how can I be sure that somebody calling ((:my-fn my-ns/my-hashmap)) doesn't have a typo in ':my-fn'?

phronmophobic18:06:41

where is this :my-fn coming from?

noisesmith18:06:58

you don't, but you also don't have any guarantee when they use resolve, and if they don't need resolve than you already have the hard coded function list somewhere

wombawomba18:06:14

It's just an example of one of the functions I'd be using

wombawomba18:06:44

Yeah I'm not going to be designing for anybody to use resolve :)

noisesmith18:06:14

if you already have the hard coded list you can just use defn

wombawomba18:06:44

Sure, I could do that

wombawomba18:06:29

But it'd be a whole lot of extra work, and possibly a pain to maintain

wombawomba18:06:35

So I'd prefer not to

Michael J Dorian18:06:22

So will programmers be manually typing calls to automatically generated functions?

wombawomba18:06:26

Also while we're on this topic, is there a way to get autocomplete in e.g. nrepl to work the same way as for a regular functioning if I use a top-level hashmap?

wombawomba18:06:04

I'd also like for clojure.repl/source to work

phronmophobic18:06:05

ok, sounds good. without knowing where this hashmap that is being used to generate specs and intern vars is coming from, then it’s hard to give more specific advice. if you’re autogenerating functions based off some server schema file that’s used by multiple servers and languages, then that could make sense. if you’re autogenerating functions based off of a hashmap that’s defined in a let statement, then there’s probably an easier, more flexible way.

wombawomba18:06:50

Yeah it's more like the former :) not really multiple languages, but both cljs and clj at least

wombawomba18:06:21

I could skip the let and put the 'generated' fn name in the macro call, which I guess would help me avoid this, but I want to enforce a one-to-one mapping between the things I'm iterating over and the functions.

phronmophobic18:06:38

if the autogenerated data source is still clj(s), then simply having code in cljc should allow you to have more straightforward approach than autogenerating functions

phronmophobic18:06:17

it’s also worth noting that intern is not available in cljs, so that approach will not work

wombawomba18:06:56

So returning to my original question, is there no way to compute a symbol and then call def(n) with that symbol?

noisesmith18:06:19

via eval (not available in cljs)

noisesmith18:06:26

or a macro, in limited cases

wombawomba18:06:48

Which limited cases?

noisesmith18:06:00

ones where the symbol is a compile time literal

wombawomba18:06:53

Hmmm okay. Guess I'm not completely sure what constitutes a compile time literal. What's an example (of a compile-time literal that can be built through a computation)?

phronmophobic18:06:43

there might be some workaround, but it’s very difficult to provide more specific advice since it would depend on the use case. I’m not sure how to suggest more things to try without some example code of what you’re trying to do

wombawomba18:06:55

Well, I'm basically trying to do the exact thing I typed up in the original question, but in a loop

wombawomba18:06:17

(at the top level of a particular namespace)

wombawomba18:06:40

I suppose one thing I could do is manually spell out all all the bindings, then have some separate compile-time logic that verifies that those bindings are an exact match for the data structure I'd be generating them from.. but that seems a bit inelegant to me

phronmophobic18:06:03

it sounds like you want some type of instrumentation and validation. here are some resources that might provide inspiration: • https://github.com/jeaye/orchestrahttps://clojure.org/guides/spec#_instrumentationhttps://github.com/clojure/core.typedhttps://clojure.org/guides/spec#_using_spec_for_validation

phronmophobic18:06:09

clojure isn’t all in on compile time checking. however, clojure does have other facilities for verification, validation, catching errors before production, etc.

wombawomba18:06:13

I guess if it is interesting, I might as well go into a bit more detail about what I’m doing. Basically I have a somewhat complex HTTP API that’s defined via reitit (https://github.com/metosin/reitit) routes, and that I’m calling in multiple different projects (both clj and cljs). I want to generate bindings for all these routes such that: 1. I can use them as regular fns in a clj(s) repl. 2. I get compile-time guarantees that they’re being used correctly in my clj(s) code. 3. I get compile-time guarantees that there are no bindings missing, and there are no ‘extra bindings’ (e.g. if a route is removed). 4. I get nice autogenerated documentation for them.

raspasov18:06:52

Not sure if this helps but:

(defmacro foo [m]
  `(defn ~(symbol (:k m)) [] (+ 1 1)))

(defmacro doseq-foo [a-macro & args]
  `(do
     ~@(map (fn [arg] `(~a-macro ~arg)) args)))

raspasov18:06:08

(doseq-foo foo {:k “f20”} {:k “f21"} {:k “f22”} {:k “f23"})

raspasov18:06:22

“generates” all of those functions

wombawomba18:06:37

Yeah that’s pretty close to what I want to do

raspasov18:06:10

I agree with other though that there’s probably a simpler way to achieve whatever you’re trying to do…

wombawomba18:06:47

The problem I guess is that the route data actually looks more like:

["/foo"
 ["/bar" ...]
 ["/baz" ...]]
…and I need to ‘flatten’ it before defining the functions.

raspasov18:06:51

This kinda of macro black magic is veeery rarely the only solution 🙂

wombawomba18:06:17

I don’t see another way to get the stuff I listed above though

ikitommi18:06:22

you can flatten the route tree with reitit.

wombawomba18:06:25

If you know of one I’d be all ears

ikitommi18:06:01

(-> ["/foo"
     ["/bar" ...]
     ["/baz" ...]]
    (r/router)
    (r/routes))

ikitommi18:06:38

internally the trees are always flattened, enables huge perf wins.

wombawomba18:06:12

@U055NJ5CC okay cool thanks, I was actually trying to do something like that

wombawomba18:06:50

specifically I’m trying to do this:

(doseq [[path data] (reitit.impl/resolve-routes my-routes (reitit.core/default-router-options)),
        :let [path-params (get-in data [:parameters :path])]
        [method spec] (dissoc data :parameters)]
  (defn-my-binding path method spec))

wombawomba18:06:16

I guess your suggestion is cleaner though 🙂

wombawomba18:06:39

either way, my problem of “how do I actually define the bindings” is still kind of unsolved

raspasov18:06:38

Not sure if this helps it:

(defmacro doseq-foo [a-macro args]
  `(do
     ~@(map (fn [arg] `(~a-macro ~arg)) args)))
Now you can do:
(doseq-foo
  foo
  [{:k "f40"}
   {:k "f41"}
   {:k "f42"}])

raspasov18:06:21

But unless you have the

[{:k "f40"}
   {:k "f41"}
   {:k "f42"}]
“ready” at compile time… it won’t work… I believe

wombawomba18:06:10

Hmm yeah, the thing is that I do have all the routes at compile-time, so I suppose maybe it could work?

phronmophobic18:06:11

does it matter if the vars are available in both clj/cljs or is it mostly for convenience at the clj repl?

wombawomba18:06:40

Well ideally I'd like to use the bindings in both cljs and cljs

wombawomba18:06:57

If the repl experience isn't as good in cljs then that's fine

raspasov18:06:41

@wombawomba I believe the cljs macros need to be in .cljc file (not sure if you’ve dealt with that)

wombawomba18:06:57

Yeah I do have all of this in cljc

phronmophobic18:06:21

if the routes are available at compile time. I would probably do something like

(def-routes-and-schemas-and-fns 
   ;; routes
   [[route-definition-1]
    [route-definition-2]
    ...])

phronmophobic18:06:31

and have that macro generate all the stuff you need

wombawomba18:06:58

Okay yeah that sounds like a good solution to me

wombawomba18:06:04

I assume that'd work even if the args would actually be (flatten-routes (generate-routes))?

wombawomba18:06:01

(the routes are defined as a tree, so I need to flatten them first)

phronmophobic18:06:49

macros allow for syntactic abstraction, so I would just describe the routes however is most appealing and have the macro figure out how produce the right code (flattening/etc)

4
phronmophobic18:06:33

the way I like to think about it, is use whatever form I would use if I was writing pseudo code on paper or at the whiteboard and then create the macro to match that syntax

wombawomba18:06:33

Anyway, thanks for the help everyone 🙂

✌️ 4
vlaaad20:06:53

fun stuff:

(defn fn->sym [fn]
  (when-let [[_ str] (->> fn
                          class
                          .getName 
                          Compiler/demunge
                          (re-matches #"^([^/]*?/[^/]*?)(--\d\d\d\d)?$"))]
    (symbol str)))

(fn->sym assoc) => clojure.core/assoc
(fn->sym inc) => clojure.core/inc

metal 4
souenzzo22:06:54

Clojure.spec.alpha has this function. My codebase too I use to test if some functions are defined in the right namespace (naming conventions etc'..)

Alex Miller (Clojure team)22:06:25

just be aware that demunge is best effort and will not work in all cases (importantly both - and munge to and demunge can't know which is right)

Alex Miller (Clojure team)22:06:41

there may be other cases that don't work

Alex Miller (Clojure team)22:06:46

(also, spec 2 no longer has this function because specs are always resolved in terms of symbols, not as function objects)

👍 8
Alex Miller (Clojure team)22:06:23

changes may need to be made in the future wrt to mappings from fns to classes (class names can get too long for the filesystem due to nesting) and at that time, this hack will also stop working for those classes

Alex Miller (Clojure team)22:06:56

as long as you understand all those caveats, have at :)

noisesmith20:06:59

ser=> (fn->sym (fn foo []))
nil

vlaaad20:06:08

this is right!

vlaaad20:06:30

and expected

noisesmith20:06:31

user=> (-> (fn foo []) class .getName)
"user$eval208$foo__209"

noisesmith20:06:41

OK - just saying, it does have a name

vlaaad20:06:05

you can't resolve it

noisesmith20:06:15

your code doesn't use resolve

noisesmith20:06:30

but your use case is to be able to resolve and thus see new definitions?

vlaaad20:06:44

ah yes, too little context 🙂

vlaaad20:06:57

my use case is finding var from fn

vlaaad20:06:41

to be able to show docstrings for defed functions

vlaaad20:06:49

so yes, what I actually need is fn->var with resolve call in the end

vlaaad20:06:42

Yay, docstrings on functions coming to #reveal!

metal 4
Ian Fernandez20:06:33

Guys, how many libs we have to support gRPC on clojure?

raspasov20:06:15

before you go for gRPC, make sure you really can explain why you need it; in my little experience with it, it doesn’t work well with the Clojure philosophy of doing things; it’s more of a Golang tool

☝️ 4
andy.fingerhut20:06:50

I do not know how many, but one named protojure shows up in a Google search for "clojure grpc". There are also several examples of using Java interop from Clojure to call Java gRPC methods, which for some Java libraries is a very reasonable thing to do in a Clojure program.

☝️ 8
Ian Fernandez21:06:52

just to know, in my company we're having a discussion to know if it's possible to adopt gRPC and I was suggesting some red flags, I saw that only had protojure and java API integration.

andy.fingerhut21:06:36

Clojure is hosted on the JVM as a design choice, not an accident, specifically to make it straightforward to take advantage of the huge number of Java libraries that exist.

Ian Fernandez21:06:01

yup, but not too much stuff are made in Java using gRPC

andy.fingerhut21:06:23

That I have no knowledge to share.

Ian Fernandez21:06:21

and I thought that gRPC was not a good choice, maybe some day Avro?

andy.fingerhut21:06:40

I don't know if that is much of a minus, if the Java support works, but I also have no experience reports to share with you, good or bad, in that regard. I mean, one of the purposes of gRPC of course is to have a language-independent data communication mechanism, but that doesn't mean that all languages support it equally well.

👍 4
raspasov21:06:42

@U9ABG0ERZ no problem! If your company is mostly Clojure, I would stick with Clojure’s transit where possible

clj 16
chrisulloa21:06:54

“it doesn’t work well with the Clojure philosophy of doing things” I disagree with this, gRPC is language independent and has a really solid way of defining service contracts. That being said I probably wouldn’t write a gRPC service in a non-statically typed language like Clojure, but a client would work just as fine.

raspasov21:06:26

@UDBM9Q0VC have you used gRPC from Clojure?

chrisulloa21:06:38

just as a consumer of a grpc service

hiredman22:06:59

I think the primary complaints I have heard (and some people really hate it) have been about the need to edit and re-generate the generated code. This is fundamentally because people are just wrapping the java libraries. From what I have read(and at one point I was thinking to implement a "native" protobuf library in clojure, so I have read a bit) there is nothing stopping an inherently more dynamic experience on clojure, just no one has done the work yet, so you likely will run into all the same annoyances as others

☝️ 12
lukasz22:06:12

We have a home grown RPC library, based on HTTP and Avro - the lack of recompilation step is a huge win. I've looked at gRPC and it has a really big surface, requires ProtoBuf recompilation and if your frontend (js/cljs) has to call your API/RPC server - there is (was? ) no good answer apart from a half-baked proxy. Oh, and also - it requires using HTTP2, which might not work in your setup (for example AWS ELB (classic) doesn't support it)

chrisulloa01:06:01

Yeah, I totally agree the compilation step is a pain. And there are similar issues using protobuf outside of gRPC with Clojure or Java. But at the cost of having a really solid contract, it makes total sense for some use cases. In our case we were building out a replacement for an old https://docs.geoserver.org/latest/en/user/services/wfs/reference.html that traditionally uses RPC and XML. I think there are some great use cases for it but at the same time I could understand the frustration of using it when there are simpler more popular solutions.