Fork me on GitHub
#beginners
<
2021-09-21
>
zackteo00:09:45

Hello I can do a macro like this ...

(defmacro test1 []
  (let [h 1]
    h))
but how do I get it to work if I want to use a syntax quote? (let ...)`

emccue00:09:06

if you want to "escape" a value inside a syntax quoted form

emccue00:09:16

(let [h 1]
  `(do ~h))

emccue00:09:30

the ~ will do it for you

emccue00:09:55

there is also [email protected] which expands out sequences inline

emccue00:09:19

(let [abc [1 2 3]]
  `(do [email protected]))

zackteo00:09:24

I want to get this to work actually ... but not sure where to put the ~

(defmacro test1 []
  `(let [h 1]
     h))

emccue00:09:35

which would expand to (do 1 2 3)

emccue00:09:53

wait so you want the let to be in the actual code?

emccue00:09:13

that will work, but as a shorthand, use h#

emccue00:09:24

this will replace the symbol with a unique one

emccue00:09:34

so instead of h it will be h_123421

emccue00:09:47

(defmacro test1 []
  `(let [h# 1]
     h#))

emccue00:09:21

which is somewhat needed for macros since otherwise you could interfere with local variables

zackteo00:09:49

right! okay nice I got what I wanted to do working 🙂

zackteo00:09:06

but have a feeling im not doing best practices for now

zackteo00:09:21

but I guess I have to explore to figure out what makes best sense

zackteo02:09:58

Hello if I have a macro that takes in a vector of symbol and key, how do I get the symbol and remap the value of it.

zackteo02:09:16

I can get "a" but as a string with

(defmacro test1 [v]
  `(let [[email protected]]
     ~(str (first v))))

(test1 [a :az
        b :bz])

zackteo02:09:41

hmmm perhaps I can put them as keywords first ...

(defmacro test1 [v]
  `(let [[email protected]]
     ~(mapv keyword (mapv first (partition 2 v)))))

(test1 [a? :az
        b :bz])

zackteo03:09:36

Let me think of how to phrase my question better...

zackteo03:09:34

Okay, I have reached a point in my macro where I get

((:tested? true :name "A")
 (:tested? false :name "A")
 (:tested? false :name "B")
 (:tested? false :name "C"))
and what I want to do is to within a let binding, remap tested? and name to the corresponding values

zackteo03:09:37

hmm okay I have a specific question ....

zackteo03:09:03

How do I get something like ...

(defmacro test1 [a body]
  `(let [a []]
     [email protected]))

(test1 z (count z)) => 0
to work?

zackteo03:09:20

ooo! I got it

zackteo03:09:30

(defmacro test1 [a body]
  `(let [~a []]
     [email protected]))

alexmiller03:09:23

usually you'd want (defmacro test1 [a & body] ... - the body is a seq of exprs and then you unquote splice them into the result

alexmiller03:09:40

for example see (source when-let)

zackteo03:09:33

Yeap! I have it as [a & body] but was trying to get a minimal example so i left out the & now i have another issue though and I'm not sure how to proceed ... I want to do something like ...

(defmacro test1 [a & body]
  `(let [(interleave (map symbol symbols#) group-type#)]
     [email protected]))

alexmiller03:09:39

in this and most of the steps above I have no idea what you're trying to do. in general macro stuff is easier to answer if you can state a) what a caller of the macro should write and b) what the macro output will be. doing this here helps others help you but also you'll find it often may help you see the answer yourself

zackteo03:09:01

where (interleave (map symbol symbols#) group-type#) is

((tested? true name "A")
 (tested? false name "A")
 (tested? false name "B")
 (tested? false name "C"))

zackteo03:09:40

wait. My bad. Let me try to do that hmmm

alexmiller03:09:06

and another helpful tip is to use macroexpand-1 if you haven't found it yet - you give it a quoted form and it will expand the macro

alexmiller03:09:46

(defmacro test1 [a & body] `(let [~a []] [email protected]))
(macroexpand-1 '(test1 abc false true))
;;=> (clojure.core/let [abc []] false true)

zackteo04:09:23

Alright! I guess I was having trouble explaining my problem because I'm new to macros and also because it is a coding task (writing of the macro) I received for a role, so am trying to do it myself while seeking help for portions of the problem

alexmiller04:09:07

a macro is just a function from input code to output code. if you can write down exactly what the input and output are, the macro is easy

zackteo04:09:30

hmmm let me try proceeding that way then 🙂 , felt some difficult also because I feel that macro is pretty complicated but let me give it another shot ...

alexmiller04:09:21

don't worry about the macro, just start from the user writes ... the code that is run is ...

seancorfield04:09:13

"The first rule of Macro Club is... don't use macros" 🙂

👍 4
alexmiller04:09:34

I think we're past rule 1 :)

seancorfield04:09:50

Yeah, but it's always worth a reminder...

zackteo04:09:32

Actually now that I look at it ... I'm really only left with this one step ... which I guess could be described as how do I do something like apply for a let I want to do

`(let [(interleave (map symbol symbols#) group-type#)
      ~group-data group#]
  [email protected])
but I need to remove the () since (interleave (map symbol symbols#) group-type#) gives (treated? true disease-name "A") instead of treated? true disease-name "A" and I can't use [email protected] as I will get Unable to resolve symbol: symbols# in this context

zackteo04:09:34

(let (vec (interleave (map symbol symbols#) group-type#))) would work too but it doesn't seem to work that way ><

zackteo05:09:01

Or is this an impossible task? Cause I can do

`(let [x# (interleave (map symbol symbols#) group-type#
      ~group-data group#]
           x#)

zackteo05:09:13

but not

`(let [x# (interleave (map symbol symbols#) group-type#
       (first x#) (second x#)
      ~group-data group#]
           x#)

seancorfield05:09:15

@zackteo You might do better writing this as a function that takes a symbolic expression and returns a symbolic expression.

seancorfield05:09:39

That will let you get the logic of the macro right without worrying about quotes and splices and so on.

zackteo06:09:47

Okay I got it working to the same point and eliminated a bunch of quotes and splices etc but am left at the same issue of the let binding

zackteo07:09:36

Okay ... i made progress ... but is there a way to do the following but in a loop depending on items in bindings/group-tyoe#?

(let [~group-data group#
               ~(first bindings) (first group-type#)
               ~(first (rest (rest bindings))) (second group-type#)]
           [email protected])

seancorfield07:09:27

I'll just repeat my earlier advice: write a function that takes a quoted symbolic form and debug it that way.

zackteo07:09:03

Okay! I think I have an idea after taking a shower as well. But thanks @U04V70XH6! I think the way I was working with symbols before caused a lot of additional difficulties which made me give up on starting with a function

zackteo07:09:48

Oh. I also realise that I had misread your earlier advice as remove the ` where possible to eliminate confusion with slices etc

zackteo13:09:48

@U04V70XH6 with all due respect, I seem to realise again that the problem being defined as taking in

[treated? :treated
 disease-name :diagnosis]
rather than
['treated? :treated
 'disease-name :diagnosis]
creates a bunch of problems and strange behaviour since treated? and disease-name can't be evaluated

zackteo13:09:49

I did solve my problem but in a unoptimal and hard-coded manner for now hmmm

zackteo02:09:24

Ended up realising that the use of recursion seems to be the most idiomatic and natural way. So went with that o:

kennytilton23:10:28

Writing a macro as a job application coding task? Damn. In Common Lisp I never went a day without writing a macro, but with Lisp-1 my only macros are in my dataflow library Matrix, where there is lots of boilerplate to hide, and an anaphoric self are vital to the DX.

👍 1
zackteo07:09:34

Okay I think I can make a recursive macro but is there a way to do so without recursion ?

quan xing07:09:15

I was write the practice code in vscode + calva. why show syntax error when I press CTRL+Enter for run code

seancorfield07:09:33

What is the rest of that error message? Can you copy'n'paste the text instead of a screenshot?

quan xing08:09:53

Syntax error compiling at (d:\E\clojure\clojure_code\src\vector\example.clj:5:1). ; Unable to resolve symbol: println in this context

pez12:09:40

Maybe you haven’t loaded the file? ctrl+alt+c enter. Calva’s REPL needs a first loading of something before it finds even core functions.

quan xing00:09:56

ok, thank you

fabrao13:09:21

Hello all, I have a defmulti that I use as validation an array like

(defmulti processar-no (fn [{:keys [tipo criar]} _] [tipo criar]))
(defmethod processar-no ["Projeto" "NÃO"] [{:keys [valor idProjeto]} ac]
  ...)
(defmethod processar-no ["Projeto" "SIM"] [{:keys [valor idProjeto]} ac]
  ...)
How do I configure a method for default stuff?

schmee13:09:09

(defmethod processar-no :default …) :thumbsup:

fabrao13:09:47

thank you, I thougth I have to use something like [:default ...

Chase17:09:04

So I am trying to build out my first big project, a full stack web app and was hoping for some general guidance.

Chase17:09:17

I am using Reagent/Re-Frame on the frontend and ring/reitit/jetty on the backend and currently have the backend and frontend connected correctly but it's basically just the front end creating the needed js and my backend slurping in the index.html . I'm a little confused though on how to actually start sharing more dynamic data between the two.

Chase17:09:31

I was thinking of having the frontend basically just handling the UI but when the user submits a form or login or whatever, I want that data to be sent to the backend where it will process it, query any needed api's and/or db for data, and then send that response back to the front end to display. Am I thinking about all this correctly?

Chase17:09:46

I'm wondering if I should just be using the front-end for all of this (since it's supposed to be a SPA anyways, right) and then what do I even need a backend for? Should I just be using a "backend as a service" like firebase instead?

Chase17:09:56

I like my original idea of having the backend doing all the heavy lifting besides UI but then should I be using reagent/re-frame vs just doing it all with server side rendering with html templates? How exactly do I send a form submitted data to my backend and then vice versa? What should I be searching for there.

Chase17:09:59

Sorry if this is too general but I'm not sure where to ask basic web dev questions with a clj/cljs slant.

Chase17:09:15

here is the current repo so far if it helps to see if I'm setting this up correctly: https://github.com/Chase-Lambert/ai

Chase17:09:34

I'm trying to figure out how I'm going to send the data posted in the frontend/ai/core.cljs query form to my backend/ai/query.clj api business logic.

Apple18:09:36

I learn from this book https://pragprog.com/titles/dswdcloj3/web-development-with-clojure-third-edition/. There's source code available for download. On the client side you can use cljs-ajax lib to send your client side of data(encapsulated in a map) to backend by json/edn format. On the backend use metosin/reitit libs to parse the client http request and you got yourself back a map for backend processing. The same libs takes care of sending the result to client, again as a map. Another good resource for what libs to use is this https://luminusweb.com/docs/guestbook.

Chase21:09:31

Ok, yep, I got that book. Time to dig in more. I'll check out that library too. Thanks!

sheluchin18:09:32

{:a 1 :b 2} -> {:x/a 1 :x/b 2} Is there some to way to namespace all the keys in a map like this without iterating over all the keys using something like reduce or map?

dpsutton18:09:05

is it a map literal in the file or a value?

vncz18:09:26

update-keys in 1.11? trollface

dpsutton18:09:32

if a literal, you can use this shorthand #:x {:a 1 :b 2} . If it is a value there is no way to do it without iterating over all of the keys

quoll18:09:29

Well… there’s not NO way to do it:

(eval (read-string (str "#:x" {:a 1 :b 2})))
😜

😂 3
quoll18:09:33

It’s done with a reader macro, so using the #:x prefix can’t be done with a macro. The only other way would be to use update-keys in the new 1.11.0-alpha2 (https://github.com/clojure/clojure/blob/b8132f92f3c3862aa6cdd8a72e4e74802a63f673/src/clj/clojure/core.clj#L7952)

(update-keys {:a 1 :b 2} #(keyword "x" (name %)))

César Augusto21:09:02

@dpsutton what do you mean when you say a value? You mean manipulating it through a symbol?

dpsutton21:09:51

(let [x {:a 1 :c 2}] x) x is a value in this expression. contrast that with the literal {:a 1 :c 2}

👍 1
sheluchin18:09:23

value... okay, got it, ty

dpsutton18:09:48

you could also make a wrapper type that intercepted the calls to lookup and handled the namespace stuff but that's super not recommended unless you have profiling reasons to do so

sheluchin18:09:25

That's a little beyond my needs at the moment, but I am curious... what would you use to implement the wrapper type? I haven't started doing stuff like that yet.

noisesmith18:09:34

to make a new type you usually want deftype

sheluchin18:09:50

Ah, this is covered in that chapter in Brave that I keep deferring for some reason 🙂 almost there.

noisesmith18:09:39

also, new data types are needed very rarely in clojure, in comparison to most other languages

noisesmith18:09:34

then you'd implement all the protocols a clojure hash-map implements with apropriate methods for the behavior you need

noisesmith18:09:58

potemkin has def-map-type which is meant for this use case (as a warning, it's explicitly a library of "ideas that are almost good", and wanting to do something potemkin offers is probably a sign you want to step back and redesign...) https://github.com/clj-commons/potemkin

João Galrito18:09:17

hello everyone. is there a way do something similar to agents but with a ring-buffer as a queue of actions?

João Galrito18:09:43

i don't want to start the next action until the previous is finished, but I'm also only interested in the latest action

João Galrito18:09:11

so if multiple actions are sent to the agent while it

João Galrito18:09:25

it's doing something, I can discard all but the last one

noisesmith18:09:38

@joao.galrito the closest match to that behavior I know of is using a core.async channel with a dropping buffer

noisesmith18:09:22

there's probably a less complex / better performing way to do it with a java queue if you don't need core.async's other features

Ed19:09:27

@joao.galrito if you just want ignore all but the latest message, you can do something like this

(let [a (agent :thing)]
    (send a (fn [x]
              (when (-> *agent* .getQueueCount (= 1))
                (prn 'do-latest-thing)))))
which will skip messages when there are more than one in the queue.

Ed19:09:17

but core async is a more generalised system that gives you better control 😉

Brandon Stubbs23:09:25

Wondering what the more idiomatic way would be to write the following?

(loop [[{size :size :as x} & rest] [{:size 1 :foo "1"}
                                    {:size 2 :foo "2"}]
         res []
         low 1]
    (if (nil? x)
      res
      (let [high' (+ low size)]
        (recur
          rest
          (conj res
            (assoc x
              :low low
              :high high'))
          high'))))
Basically looping through and adding the low and high range for each of the maps based on their size

Ed23:09:48

maybe

(let [data [{:size 1 :foo "1"} {:size 2 :foo "2"}]]
       (->> data
            (reductions (fn [{:keys [high]} {:keys [size] :as x}] (assoc x :low high :high (+ high size))) {:high 1})
            (drop 1)))
???

dpsutton23:09:53

i think that's just reducing over the collection. i don't see any control flow the requires an explicit loop unless that's your preferred style

Apple23:09:54

the low is a state so reduce can only save you the if (nil? part when compared to loop in this case.

ghadi23:09:24

try to avoid thinking in loops for data processing

ghadi23:09:38

especially as you're getting familiar with the clojure.core library

dpsutton23:09:10

also, O(2n) = O(n). If your data isn't huge, traversing twice isn't the end of the world 🙂

ghadi23:09:19

someone should show their reduce answer so we're not just pontificating

Ed23:09:46

put one using reductions in a thread 😉

ghadi23:09:46

reductions is like reduce, for hoarders

ghadi23:09:07

the problem above was: calculate absolute bounds for a bunch of entities that you know the size of

Ed23:09:07

yeah ... I'm not sure that the loop code does that ....

dpsutton23:09:07

(:coll (reduce (fn [{:keys [low coll] :as state} {:keys [size] :as x}]
                 (-> state
                     (update :low + size)
                     (update :coll conj (assoc x :low low :high (+ low size)))))
               {:low 1 :coll []}
               [{:size 1 :foo "1"}
                {:size 2 :foo "2"}]))
and
(map (fn [{:keys [size] :as x} running-total]
       (assoc x :low running-total :high (+ size running-total)))
     [{:size 1 :foo "1"}
      {:size 2 :foo "2"}]
     (reductions + 1 (map :size [{:size 1 :foo "1"}
                                 {:size 2 :foo "2"}])))

❤️ 2
dpsutton23:09:45

i think captures what's going on

ghadi23:09:18

you can get the highest bound with: (reduce + 0 (map :size things)) and all the high bounds with (reductions + 0 (map :size things))

ghadi23:09:11

I'm not a fan of the reduce accumulators (or loop-carried states) above having two things in it

Brandon Stubbs23:09:52

So the preferred would probably be the map and reductions approach as also shown by @dpsutton?

ghadi23:09:53

makes the body deal with two things, the previous num and the output

ghadi23:09:48

something that avoids loops for data processing

ghadi23:09:08

@dpsutton’s reductions sample can be "reduced" further, heh

🎉 2
2
Apple23:09:41

(let [d [{:size 1 :foo "1"} {:size 2 :foo "2"}]]
  (loop [[{size :size :as x} & rest] d
         res []
         low 1]
    (if (nil? x)
      res
      (let [high' (+ low size)]
        (recur rest (conj res (assoc x :low low :high high')) high')))))
Although essentially the same but this looks nicer, right?

💯 2
ghadi23:09:37

it is indented nicer but three loop carried variables -> errors will abound

dpsutton23:09:45

I don’t see anything different from the original example except fewer new lines

Ed23:09:18

(let [data [{:size 1 :foo "1"} {:size 2 :foo "2"}]]
    (reduce (fn [r {:keys [size] :as x}]
              (let [low (:low (peek r) 1)]
                (conj r (assoc x :low low :high (+ low size))))) [] data))
you can remove the need to call :coll on the reduced value by using peek on the input vector??

ghadi23:09:15

for a beginner, in the example below, you can find a few subtopics to dive into: • three collection functions (map, partition, reductions) • separating concerns • the fact that clojure.core/map can take multiple collections and apply a function "across" themm • the threading macro ->> • destructuring [lo hi]

👍 2
❤️ 4