Fork me on GitHub
#beginners
<
2020-10-02
>
Nassin03:10:37

How does an alias passed to -M look like? is its value a vector of strings? (in deps.edn)

seancorfield04:10:59

@kaxaw75836 -M:foo:bar:quux will look for the three separate aliases :foo, :bar, and :quux in the combined deps.edn data (system + user + project), and then the contents of those three aliases will be merged (according to the rules explained in the Clojure CLI docs on http://clojure.org -- some are merged as hash maps, some are concatenated as vectors, some are "last one wins").

seancorfield04:10:35

I'm not sure where "vector of strings" comes in? The aliases are all read and merged before your program is run.

seancorfield04:10:56

With the latest CLI, programs started via -M or -X can read the system property that provides the location of an EDN file that you can read to get a full hash map that contains the combined alias data, the lib path data, etc -- the "basis".

Nassin04:10:12

:aliases {:dev         {:extra-paths ["dev"]}
           :nrepl       {:extra-deps {cider/cider-nrepl {:mvn/version "0.25.3"}}}
           :nrepl-opts  ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"]}

Nassin04:10:52

I'm doing clojure -A:dev:nrepl -M:nrepl-opts

Nassin04:10:04

something wrong there?

seancorfield04:10:19

You're missing :main-opts

seancorfield04:10:35

:nrepl-opts {:main-opts ["-m" ...]}

Nassin04:10:40

yeah, that works, was trying to avoid the WARNING: Use of :main-opts with -A is deprecated. Use -M instead.

seancorfield04:10:02

clojure -M:dev:nrepl:nrepl-opts will avoid that warning.

seancorfield04:10:56

Did that work @kaxaw75836?

Nassin04:10:03

yep, that work thanks, was not seeing the warning again when reverting to -A, but removing .cpcache put it back on

seancorfield04:10:55

Ah, yes, -Sforce is your friend there

seancorfield04:10:36

There's definitely going to be a bit of adjustment in the CLI world as all the projects and tutorials get updated to the latest set of options. I've got to go through my open source projects and update the READMEs -- and update clj-new so it generates all the up-to-date deps.edn and project docs etc.

Nassin04:10:43

I can follow along now, took a while to figure -M -m in the cli

seancorfield04:10:27

Yeah, there's quite a bit of discussion about the -M -m "repetition" but it's the start of a grand plan that will bring more functionality over the next few releases...

seancorfield04:10:40

The general guidance is that you use just one of -A (for REPL dependencies), -M (for running any main opts), or -X (for executing functions) and just specify all the aliases you need via that one option.

seancorfield04:10:02

The confusing part right now is that -A still executes :main-opts -- which it will stop doing at some point.

seancorfield04:10:26

(which is why we still have -R and -C -- but the real solution is to split :main-opts out of the aliases that you would otherwise want to use with -A -- as you have done above -- so you can do clj -A:dev:nrepl to start a REPL but clj -M:dev:nrepl:nrepl-opts to run the nREPL command-line)

Nassin04:10:40

you mean clj -A:dev:nrepl if you want to start a REPL programmatically for example correct? and clj -M:dev:nrepl:nrepl-opts to start it from CLI?

Nassin04:10:23

yeah, typo 😉

practicalli14:10:28

@kaxaw75836 I've simplified my aliases to just use -M (or -X where supported). Lots of examples here https://github.com/practicalli/clojure-deps-edn

vlaaad14:10:53

hey, small correction: reveal supports -X flag

vlaaad14:10:55

e.g.

{:extra-deps {vlaaad/reveal {:mvn/version "1.0.130"}}
 :ns-default vlaaad.reveal
 :exec-fn repl}

vlaaad14:10:33

then you can use it as is:

$ clj -X:reveal
Clojure 1.10.1
user=> 1
1

vlaaad14:10:02

override args:

$ clj -X:reveal :title '"hello"'
Clojure 1.10.1
user=> 1
1

vlaaad14:10:24

use different type of repl:

$ clj -X:reveal io-prepl :title '"hello"'
1
{:tag :ret, :val "1", :ns "user", :ms 0, :form "1"}

vlaaad14:10:30

it's all already there

practicalli17:10:23

Awesome. I've added the Clojure exec config to the existing aliases in the repository. So the aliases can be used with either -M or -X, with example of using -X to call other REPLS.

dharrigan14:10:36

Say you have this (def foo [{:a "b" :e "f"} {:a "c" :g "h"}])

dharrigan14:10:57

Is there a beter way to remove all a's, than this?

dharrigan14:10:00

(mapv #(dissoc % :a) foo)
[{:e "f"} {:g "h"}]

dpsutton14:10:24

that seems to line up semantically with exactly what you are trying to do, ie, remove the :a key from vector of maps. Is there something deficient about it? Wondering why you would ask

dharrigan14:10:17

Perhaps there was some deep knowledge within the standard library that would do that 🙂, you know, one function and all 🙂

dharrigan14:10:35

but I'm happy that my first attempt hit home 😉

dharrigan14:10:13

Always looking to see if I can improve my understanding of the standard library, by learning about a function that I don't know of yet.

dharrigan16:10:33

I have an API that makes some decisions based upon the input. Depending on a value of one of the input, a requires-resolve is invoked to load in a namespace. Are there any downsides (memory?) of that requires-resolve being invoked again and again after the first time (i.e., once the namespace has been resolved)?

alexmiller16:10:53

not really. some perf impact but I assume it's negligible unless you're calling it in a hot loop

dharrigan16:10:14

great, thank you. no. it's called infrequently 🙂

Michael W18:10:14

(def colla [{:email "" :ip "10.0.0.1"} {:email "" :ip "10.0.0.2"}])
(def collb [{:email "" :new_email ""} {:email "" :new_email ""}])
How to merge 2 lists of maps where they may share the same field? I have 2 vectors of maps, they may share an :email key, I would like to merge the maps. The only solution I have come up with is really ugly, is there a better way?
(mapv (fn [i]
        (let [email (string/lower-case (:email i))]
          (merge i (first (filter #(= (string/lower-case (:email %)) email) collb))))) colla)
[{:email "", :ip "10.0.0.1", :new_email ""} {:email "", :ip "10.0.0.2", :new_email ""}]

souenzzo13:10:37

clojure.set solution

(let [colla [{:email "" :ip "10.0.0.1"}
             {:email "" :ip "10.0.0.2"}]
      collb [{:email "" :new_email ""} 
             {:email "" :new_email ""}]
      xf-lower-email (map (fn [x]
                            (if (string? (:email x)) 
                              (update x :email string/lower-case)
                              x)))]
  (clojure.set/join (into #{} xf-lower-email colla)
                    (into #{} xf-lower-email collb)
                    {:email :email}))

Michael Stokley18:10:41

what do you want to happen in the event that the maps share the same key? i think with merge, the last value in wins

Michael Stokley18:10:36

there's also merge-with

Michael Stokley18:10:59

which, i believe, lets you pass in an explicit merging strategy

ghadi18:10:42

merging lists is not a thing. You can concatenate lists, or merge maps

ghadi18:10:09

assuming you mean "merge maps repeatedly", @michael740’s question is relevant -- what is the desired behavior with two maps?

ghadi18:10:49

and if that's a bad assumption -- perhaps an example of what you want would clarify

Michael W18:10:54

I updated the question with the output from the working solution, It works fine, but it looks wrong to me, like I am missing a fundamental core function that would simplify the operation.

Michael Stokley18:10:58

you can map over n collections, too

Michael Stokley18:10:04

(map merge
     [{:name "michael"} {:name "stokley"}]
     [{:name "andy"}    {:name "fingerhut"}])
;; => ({:name "andy"} {:name "fingerhut"})

Michael Stokley18:10:22

(map + [1 2] [3 4])
;; => (4 6)

Michael Stokley18:10:48

@michael819 :man-shrugging: ^ ?

Michael W18:10:10

The vectors aren't the same size, and the map shape is slightly different.

andy.fingerhut18:10:14

If these two vectors can be very long, and you are concerned about efficiency of this operation, then creating a map from the second vector, where the key is the :email value of the map, and the corresponding value is the map with the :email from the second vector, would be a good first step, so you do not need to do a linear scan of the second vector each time.

andy.fingerhut18:10:00

But given the output you want and the sample code you gave, I don't think there is a lot shorter Clojure code one could write using the core library of functions to achieve what you want.

andy.fingerhut18:10:51

And if the linear scan of the second vector is not a performance issue for you, then I would say that I don't consider the code you wrote ugly, personally.

andy.fingerhut18:10:46

Another thing to note about your code is that if there was a map in the second vector with an :email that did not appear anywhere in the first vector, it would not be included in the result. Maybe you know that this is OK from the context of where you want to do this.

Michael W18:10:07

That's actually intended, the actual maps have different sizes, and some may have an email while others don't.

Michael W18:10:05

I just thought matching field values would be something the core would include but I didn't find anything in the docs and wanted to be sure I didn't miss anything. Still wrapping my head around functional programming. Thanks for the feedback.

andy.fingerhut18:10:48

You could perhaps consider writing a separate named function like (defn my-email [user] (string/lower-case (:email user))), and then call that twice:

(mapv (fn [user]
        (let [email (my-email user)]
          (merge i (first (filter #(= (my-email %) email) collb))))) colla)

Michael W18:10:13

Yeah this was a one-off I didn't even write a function, just did in in the repl, but I would pull that out into a seperate function if I was going to prod with it.

ghadi18:10:10

user=> (group-by #(str/lower-case (:email %)) (concat colla collb))
{""
 [{:email "", :ip "10.0.0.1"}
  {:email "", :new_email ""}],
 ""
 [{:email "", :ip "10.0.0.2"}
  {:email "", :new_email ""}]}

user=> (map #(apply merge %) (vals *1))
({:email "", :ip "10.0.0.1", :new_email ""} {:email "", :ip "10.0.0.2", :new_email ""})
@michael819

ghadi18:10:28

you can build up a lookup map by the normalized email

ghadi18:10:41

then merge the right-hand side (the values) of the map

ghadi18:10:13

still, better to normalize the emails at the outset

ghadi18:10:34

also, ^. See clojure.set

andy.fingerhut18:10:51

Doh! Of course. Still haven't had first coffee of the day.

hiredman18:10:40

a kind of generic hash-equi join algorithm for 2 collections is 1. index the smaller collection (this is the hash) 2. walk each item in the larger collection using the hash to find items in the smaller to join to

hiredman18:10:57

which is kind of similar to the group-by above

hiredman18:10:43

https://github.com/clojure/clojure/blob/master/src/clj/clojure/set.clj#L111-L140 is an example which designed to work with sets, which means you lose any ordering you may want to preserve

dharrigan20:10:32

For interop, if a method has setData(Map<String, String> data), is it as simple as doing (.setData object {"a" "b" "c" "d"})?

ghadi20:10:28

yes @dharrigan, because clojure maps implement java.util.Map

dharrigan20:10:36

w00t thank you 🙂

dharrigan20:10:13

yup, works flawlessly 🙂

seancorfield20:10:36

@ghadi That begs a question which often floats around in my mind: do you tend to use class and supers rather than type and ancestors (and only switch to the latter pair when you want something outside the "type" (class) hierarchy)?

ghadi20:10:19

I have used type/ancestors probably a couple times in 10 years

seancorfield20:10:33

(and, related, does ClojureScript have class/`supers`?)

andy.fingerhut20:10:51

ClojureScript does not have class, only type

seancorfield20:10:36

I just fired up a cljs REPL to check and I see it has ancestors but of course the types of a lot of things in cljs do not have any ancestors... 👀

ghadi20:10:36

I'm usually interested in class hierarchy

ghadi20:10:13

type and ancestors are built upon class / supers, so not as primitive