Fork me on GitHub
#beginners
<
2019-09-05
>
johnjelinek04:09:07

I want to make a function that's invoked like this:

(build {:builder CfnSubnetGroupProps/builder
        :cache-subnet-group-name "sup"
        :description "sup"
        :subnet-ids ["sup"]})
where it ends up doing this:
(-> (CfnSubnetGroupProps/builder)
    (.cacheSubnetGroupName "sup")
    (.description "sup")
    (.subnetIds ["sup"])
    (.build))
is a macro the right way to go here?

johnjelinek04:09:01

I don't really want to pass the builder object in the map though.

johnjelinek04:09:20

I guess I really want to call something like (CfnSubnetGroupProps/builder {,,,}) except that builder is a 3rd party lib

seancorfield04:09:10

Looks like a pure function to me that converts a data structure to a series of method calls.

johnjelinek12:09:15

I thought about (doto, but didn't know what benefit that provides over the threading implementation

Lennart Buit05:09:04

Heya, I am having a bit of trouble with making a list of symbols with selective evaluation. So for example, given that thing is :Thing, I want to make a list that looks like (my-symbol (my-other-symbol (my-symbol :Thing))). The following works, but feels overly verbose, is there an easier way?

(let [thing :Thing]
   (list 'my-symbol (list 'my-other-symbol (list 'my-symbol thing))))
=> (my-symbol (my-other-symbol (my-symbol :Thing)))

Lennart Buit05:09:39

I tried this:

(let [thing :Thing]
   '(my-symbol (my-other-symbol (my-symbol ~thing))))
=> (my-symbol (my-other-symbol (my-symbol (clojure.core/unquote thing))))
But that appears to not be the same with that ‘unquote’ in the last pos.

yuhan06:09:03

you have to use syntax-quote with backtick ` instead of regular quote

Lennart Buit06:09:53

right, but I don’t want to fqn those symbols (because they don’t exist as def/defns). So this works:

(let [thing :Thing]
  `(~'my-symbol (~'my-other-symbol (~'my-symbol ~thing))))
=> (my-symbol (my-other-symbol (my-symbol :Thing)))

yuhan06:09:43

yeah, that's how it works if you need non-namespaced symbols

yuhan06:09:34

there's a gensym shorthand my-symbol# which may be useful for you

yuhan06:09:59

eg. for local bindings in a macro

Lennart Buit06:09:10

Hmm, that helps when you need fresh names right?

yuhan06:09:59

yeah, so that local symbols in a macro won't accidentally shadow outer bindings ('hygeine')

Lennart Buit06:09:46

right, in this case I just need the ‘exact’ symbol. I don’t think it gets evaluated. Thanks for the help!

borkdude12:09:48

@lennart.buit A bit ugly but it also does the job:

(read-string (let [thing :Thing] (format "(my-symbol (my-other-symbol (my-symbol %s)))" thing)))
(my-symbol (my-other-symbol (my-symbol :Thing)))
There's a thread about this problem here: https://clojureverse.org/t/a-macro-between-quote-and-syntax-quote/4466/10

borkdude12:09:22

They mention the backtick template macro

Lennart Buit12:09:20

haha great, I was looking for the least ugly solution :’)

Lennart Buit12:09:42

that magic quote thing is cool tho

borkdude12:09:53

personally I would go for ~' in that case, but that may also be a sign that you're doing it wrong

Lennart Buit12:09:19

yeah, I had that feeling as well, of ‘trying to do it wrong’

Alex Miller (Clojure team)12:09:38

But you might also look at the largely unused clojure.template which is in core

borkdude12:09:31

cool, maybe I could use that to implement macros in sci 🙂

Lennart Buit12:09:47

oh thats used by clojure.test right?

Lennart Buit12:09:58

I think I remember it from the internals of are

borkdude12:09:44

oh right, that's where it's used

borkdude12:09:49

that seems like the right fit for this kind of problem

Lennart Buit12:09:34

right, I was just going to add this for posterity:

(let [thing :Thing]
  (apply-template '[x] '(my-symbol (my-other-symbol (my-symbol x))) [thing]))
=> (my-symbol (my-other-symbol (my-symbol :Thing)))

borkdude12:09:32

user=> (t/apply-template '[x] '(my-symbol (my-other-symbol (my-symbol x))) [:Thing])
(my-symbol (my-other-symbol (my-symbol :Thing)))

Lennart Buit12:09:51

yeah, I was editing my own words by the power of the internet

Lennart Buit12:09:00

thanks for the insights, learned a thing or two today 🙂!

borkdude13:09:35

@lennart.buit It seems to be powered by postwalk-replace:

user=> (clojure.walk/postwalk-replace '{_x 10} '(let [x _x] (inc x)))
(let [x 10] (inc x))

borkdude13:09:19

and that it turn is just (postwalk (fn [x] (if (contains? smap x) (smap x) x)) form)) I'm in awe of the simplicity of Clojure

Lennart Buit13:09:21

yeah was just about to say, did you mean simple

Lennart Buit13:09:26

mister-edit-his-words-just-like-I-do

borkdude13:09:32

both actually

borkdude13:09:36

Simple made easy.

borkdude13:09:22

I'm the kind of person who first sends an e-mail and then reads it for corrections... so Slack is better for me than IRC 😉

Lennart Buit13:09:42

I think that I edit about 80% of the messages I send

Lennart Buit13:09:56

and I get uneasy when I cannot edit my messages, like email or WhatsApp

Lennart Buit13:09:04

most annoyingly: Wording yourself badly on GitHub, because you can edit things on GH, but not in the emails that are also sent

Lennart Buit13:09:06

I’ll stop with the off-topic

borkdude13:09:54

My solution to that is usually delete the original message and post a new one

Lennart Buit13:09:40

words of a serial-editor

DomenM14:09:22

hey, this may be something i overlooked or something really stupid i overlooked, but i’m having a problem removing a key from nested maps. could someone give it a look and explain to me why it doesn’t work the way i think it should?

(defn removex [map]
  (let [newmap (dissoc map :x)
        children (:children newmap)]
    (if (some? children)
      (assoc newmap :children (map removex children))
      newmap)))

DomenM14:09:27

so, say i have

{:a 1 :x 2 :children [{:b 3 :x 4}]}
i’d expect this to remove the :x entry from the toplevel map and overwrite the :children entry with assoc, doing the same thing to every child

DomenM14:09:05

thus producing

{:a 1 :children ({:b 3})}

DomenM14:09:34

instead i get

{:a 1 :children [{:b 3 :x 4}]}

danielneal14:09:22

Using map as a name in clojure is dangerous - you shadow the map function. Use m

DomenM14:09:37

oh my fucking god.

😅 4
danielneal14:09:42

Because this line (map removex children) is being called with your map, not clojure.core/map

DomenM14:09:01

yeah. i needed someone to tell me that option 2 is the one 😞

danielneal14:09:02

and maps are ifns too so no error

DomenM14:09:17

thanks for pointing out my stupid error

danielneal14:09:29

no worries - I’ve done it enough times myself 😄

danielneal14:09:35

usually with name

Alex Miller (Clojure team)14:09:39

there are probably some better more generic ways to do this too

DomenM14:09:29

most are probably beyond me at this point though 🙂

Alex Miller (Clojure team)14:09:09

like if your goal is to remove all the :x's, you can (defn removex [m] (clojure.walk/postwalk #(if (and (map? %) (contains? % :x)) (dissoc % :x) %) m))

Alex Miller (Clojure team)14:09:57

postwalk will walk the data (bottom-up) and replace each node with the result of the function

Alex Miller (Clojure team)14:09:21

so if you encounter something is a map and has x, remove it. otherwise no change

Alex Miller (Clojure team)14:09:28

the condition and what you do when it's encountered are completely up to the function you write, so this is a very generic technique that is applicable in a wide range of scenarios

DomenM14:09:47

wow ok. this looks nice and totally uncomplicated

DomenM14:09:45

my first try was looking at specter, but since this was a quick and dirty solution i needed i didin’t get any further than “specter is too complicated for what i need right now”, and neither did i look for purposeful solution in the standard libraries

Alex Miller (Clojure team)14:09:37

you can also do it with specter

Alex Miller (Clojure team)14:09:35

postwalk is unreasonably useful though :) once you think in that way, you see it everywhere

DomenM14:09:12

I see there’s more than one walk. what’s the difference between walk, postwalk and prewalk?

Alex Miller (Clojure team)14:09:17

walk is the generic tool. postwalk is bottom up, prewalk is top down. you almost always want postwalk

Alex Miller (Clojure team)14:09:27

postwalk-replace is useful in specific circumstances

DomenM15:09:23

oh i see. my CS education is lacking: names come from pre-order / post-order traversal i guess

Alex Miller (Clojure team)15:09:35

those are probably more precise words than bottom-up and top-down :)

Noah Bogart17:09:10

when I call swap! and pass a function that uses remove, the lazy seq is fully realized, right? i don't have to worry about wrapping it in a doall or something?

noisesmith17:09:51

it's not realized, but any further modification would need to realize it...

noisesmith17:09:19

if you are doing something side-effecting or resource-dependent in the remove call, you do need to call doall

noisesmith17:09:49

generally lazy things and side effects / contextual resources are a bad fit

hiredman17:09:42

do side effecting things in a swap! is bad too

4
Noah Bogart17:09:29

this is my current call: (swap! state assoc :events #(remove (fn [event] (same-card? card (:card event))) (:events @state)))

Noah Bogart17:09:34

there aren't any side effects except for the swap! but i'm worried that outdated data might end up in the :events entry

noisesmith17:09:10

that puts a lambda under a key

noisesmith17:09:15

remove doesn't even get called

Noah Bogart17:09:10

oh damn, i was thinking of update when i wrote that

noisesmith17:09:51

even with update, the value under the key would be passed in, you wouldn't deref inside

Noah Bogart17:09:20

removing the anonymous function, should i wrap the remove call in a doall?

noisesmith17:09:37

yes, if it needs to be coherent

Noah Bogart17:09:43

okay, good to know

Noah Bogart17:09:51

thanks, and sorry for the confusion

noisesmith17:09:27

something like (swap! state update :events #(doall (remove f %)))

noisesmith17:09:51

where f is the same as your original

noisesmith17:09:28

second guessing myself now - doall shouldn't actually be neccessary

hiredman17:09:32

doall isn't strictly required, but if you are doing a lot of removes between actual reads of :events which would force the seq, a doall might be a good idea to avoid blowing the stack

👍 4
Noah Bogart17:09:05

yeah, lots of frequent reads, frequent updates/changes

papachan19:09:54

is there any equivalent with clojure 1.10 with: (use 'clojure.contrib.shell) ?

noisesmith19:09:39

@papachan is there a feature you need that isn't offered by clojure.java.shell/sh?

noisesmith19:09:03

I tried raynes/conch but found it easier to use ProcessBuilder / Process directly (they come with the jvm)

seancorfield19:09:02

In particular "clojure.contrib.shell and clojure.contrib.shell-out migrated to clojure.java.shell".

noisesmith19:09:30

oh lol it's literally the same code

seancorfield19:09:31

That all happened as part of the Clojure 1.2 -> 1.3 cycle ("many years ago").