Fork me on GitHub
#beginners
<
2018-03-18
>
quangh07:03:15

Hi, which is the good way to transform vector of maps to map of vectors? Ex: [{:a 1 :b 2} {:a 2 :b 3} {:a 3 :b 4} => {:a [1 2 3] :b [2 3 4]}]

rauh07:03:15

@quangh apply + merge-with. Does that help?

tap08:03:16

Yeah, something like this

tap08:03:22

(apply merge-with conj [{:a [] :c []} {:a 1 :c 2} {:a :c 3} {:a :c 4}])

quangh09:03:14

@rauh @tap Thank you, that's better. I have made it with redude-kv `(reduce-kv (fn [m k v] (merge-with into m (reduce (fn [newcoll [x y]] (assoc newcoll x [y])) {} v))) {} [{:a 1 :b 2 :c 3} {:a 2 :b 3 :c 4} {:a 3 :b 4 :c 0}])`

sarna11:03:30

hey, I'm reading about seq functions and.. isn't calling into after every call horribly expensive?

rauh11:03:24

@mailmeupinside Only way to find out is to benchmark it. Comparing it with conj

sarna11:03:10

@rauh yeah, but I see that clojure does a lot of conversions between the types. maps into sets, then lists, then maps again and I'm used to doing everything using just one type

ackerleytng12:03:11

Why does

(defmacro domain
  [name & body]
  `{:content [~@body]})
and
(defmacro domain
  [name & body]
  `{:content ~(vec body)})
work but not
(defmacro domain
  [name & body]
  `{:content ~body})

mfikes12:03:54

@ackerleytng It does, in a sense. Try (domain foo inc 10) for a clue 🙂

mfikes13:03:29

Also, (macroexpand '(domain foo inc 10)) may be instructive.

mfikes13:03:55

You can change your last definition to

(defmacro domain
  [name & body]
  `{:content '~body})
if you want to imitate the first two.

ackerleytng13:03:42

oh! so ~ sort of executes what comes immediately after

ackerleytng13:03:01

hence (inc 10) becomes 11?

ackerleytng13:03:01

'~body results in (inc 10) being spliced in

mfikes13:03:03

It unquotes. To see this try (def a :hi) and then

`a
and then see what happens when you unquote
`~a

mfikes13:03:13

Yeah, but I wouldn't use the word splice for '~body, as that meaning is applicable to your form [~@body].

mfikes13:03:10

Another illustrative example building on the above:

`[a ~a b]

ackerleytng13:03:33

what does unquote really mean?

ackerleytng13:03:14

does it mean...

mfikes13:03:17

Well, think of just regular quote '

ackerleytng13:03:24

"allow eval to really happen"?

ackerleytng13:03:55

' means "for the next form, don't do anything and just pass it on exactly"

mfikes13:03:38

It means not much more than "unquote", but of course a consequence is that the evaluation can occur

mfikes13:03:16

So, '(a b c) evaluates to a list of three symbols....

ackerleytng13:03:05

as in this?

learning.core> '(a b c)
(a b c)

mfikes13:03:08

Almost like (list 'a 'b 'c)

mfikes13:03:31

But what if you didn't want the a to be quoted. (And you want to "unquote" it.)

mfikes13:03:01

That's what ~ does, but only for syntax quote

`
not regular quote
'

👍 4
mfikes13:03:08

So that describes the low-level mechanics. But, in the end, it has the effect you are describing. It punches a hole in the quoted "thing", and lets the unquoted bit through so that it can be evaluated instead of being quoted to prevent evaluation.

ackerleytng13:03:06

learning.core> `(a b c)
(learning.core/a learning.core/b learning.core/c)

mfikes13:03:40

Yeah, that illustrates that syntax quote qualifies symbols

ackerleytng13:03:46

what's the difference between a and learning.core/a?

ackerleytng13:03:01

"qualifies symbols" means taking symbols and attaching a namespace to it?

mfikes13:03:03

a by itself is a symbol that (if not a local via let or a function argument), would resolve to the namespace it is in when evaluated

mfikes13:03:13

An example. If you

(def inc 3)
then inc resolves to user/inc and evaluates to 3. But clojure.core/inc can still be used to refer to the core function.

mfikes13:03:45

So, if you are in the learning.core namespace a and learning.core/a would be the same (assuming a is not a local or fn arg)

ackerleytng13:03:12

so

`a 
sort of halts the evaluator?

ackerleytng13:03:31

cos it

learning.core> `a
learning.core/a
doesn't go ahead and resolve learning.core/a

mfikes13:03:36

Yeah, in a sense that it quotes a

mfikes13:03:04

You might ask, why have a syntax quote that qualifies symbols?

ackerleytng13:03:00

so is it something like read resolve eval print loop

ackerleytng13:03:20

and ' halts resolving (and eval)

ackerleytng13:03:31

but ` halts eval?

ackerleytng13:03:52

yes, why have a syntax quote to qualify symbols?

mfikes13:03:53

To see that, try (defmacro my-inc [x] (list 'inc x))

mfikes13:03:25

If you use it in a fresh REPL, then it will work fine.

mfikes13:03:37

But, try it with (def inc 3) at play.

mfikes13:03:23

To fix that (make your macro hygienic), you might write (defmacro my-inc [x] (list 'clojure.core/inc x))

ackerleytng13:03:19

even macroexpand-1 immediately prints 11

ackerleytng13:03:43

i was expecting it to print (inc 10)

mfikes13:03:50

Did you forget to quote the argument to macroexpand-1? You need to do (macroexpand-1 '(my-inc 10))

ackerleytng13:03:35

oh yes i did sorry

mfikes13:03:48

So you can see that this fails if inc has been redefined to evaluate to 3.

ackerleytng13:03:20

yes, it returns 3 instead

ackerleytng13:03:30

(letfn [(inc [x] 3)]
  (my-inc 10))

ackerleytng13:03:56

so the difference between defn and defmacro is that defn returns the result

ackerleytng13:03:14

and defmacro returns something that will first be evaluated, before the result is printed

Russ Olsen16:03:09

@mfikes already said this, but let me try to put it in different words. Macros are functions that you are sticking into the compiler pipeline. When the compiler reads an expression like (do-something some-value), one of the first things it does is look for a macro called do-something. If it finds a macro called do-something, the compiler will evaluate macro just like a function, passing in the code for the arguments - so in the example it will pass in some-value. The result returned by the macro is what the compiler compiles. So a macro is free to generate code that evaluates its argument, munges them somehow or ignores that completely. And if the macro ignores its arguments then they effectively disappear, since the compiler never see them.

Russ Olsen16:03:54

Just to finish the story, if the there isn't a macro called do-something then it's either a built in thing like 'if' and the compiler will generate code for that built in thing or it will generate code to evaluate the argument and call a function called do-something.

mfikes13:03:22

But instead if you were to have defined my-inc using (defmacro my-inc [x] (list 'clojure.core/inc x)) it would work and return 11

mfikes13:03:06

So, the next step is to instead just define it as

(defmacro my-inc [x] (list `inc x))

mfikes13:03:29

And this can be further simplified using unquote

(defmacro my-inc [x] `(inc ~x))

mfikes13:03:39

(which is easier to read)

mfikes13:03:30

Think of defn as defing a function that you can call at runtime. Think of defmacro as defing a function the compiler calls at compile time (during macroexpansion)

ackerleytng13:03:45

how about

(defmacro my-inc [x] (list inc x))
? it seems to work too

mfikes13:03:49

It will, unless you have used (def inc 3) before having evaluated the defmacro form.

mfikes13:03:53

This is because your last macro example is returning a list containing the value of inc (either the function value or the number 3 depending on what you have done)

ackerleytng13:03:50

thanks @mfikes, really appreciate it

ackerleytng13:03:22

i guess i'm still not completely clear but i'm better than before, thanks again!

mfikes13:03:58

No problem. Macros are a mind-bending topic 🙂

👍 4
Russ Olsen16:03:55

Tag teaming macro questions since last week!

👍 4
feihong16:03:54

hi, beginner here, i started by editing clojure using parinfer in atom. now i am trying to learn how to code clojure in spacemacs. should i try to install parinfer there as well or should i get used to smartparens? as i understand it, to get parinfer to work in spacemacs i have to switch to the develop branch which may be more unstable.

athomasoriginal16:03:59

Hi! How much experience do you have with emacs in general? How much experience do you have with programming?

Josh Horwitz16:03:31

I'm interested in this question as well. I am experienced in programming and emacs with evil-mode

feihong16:03:39

@U6GNVEWQG i’m an experienced python programmer. i’m fairly familiar with vi, but almost no experience at all with emacs.

feihong16:03:52

one reason i was attracted to parinfer at the start was because it allowed me to edit clojure like it was python, where i basically don’t have to think about the parentheses

athomasoriginal16:03:23

Yeah, your background is similar to my own when I started. I was using vi and atom/vscode. If I had to go back, especially when just learning clojure, I wouldn't even worry about parinfer. Just focus on the code and, if you want, a strong REPL environment. The tooling took way to much of my time in the beginning.

athomasoriginal16:03:08

Especially if emacs is not your your regular tool of choice. There is enough to learn in Clojure that I would strongly recommend using a simple editor like Atom. Prototepl is great. The other tooling that you will hear experienced clojurists talk about is amazing, but keep in mind, they have the experience and it comes a little more naturally.

athomasoriginal16:03:13

Again, this is coming to someone who dove into emacs without much experience, fought to make the tooling work and lost track of the original purpose: To rock out with clojure

athomasoriginal16:03:42

However, like you said, parinfer is an excellent tool, and I don't want to discourage you from using it, but if your goal is to learn an opinionated editor like emacs, and the tooling that sits ontop of it, and learn a language like clojure, this can be a lot at once

orestis17:03:17

BTW, the spacemacs clojure layer is quite good, doesn’t have parinfer but it does have rainbow parentheses (I think by default) which goes a long way towards helping you balance parens.

eggsyntax18:03:47

I've used both smartparens and parinfer (in Spacemacs), and although parinfer is excellent, I've ended up sticking with smartparens. The key is to use sexp-editing commands as much as possible, instead of just deleting/moving stuff character-by-character. That way your parens will stay balanced at all times. You may also want to turn on smartparens-strict-mode, which will force your parens to stay balanced -- that makes it harder to make mistakes. That said, it's not actually that much of a headache to be on the develop branch of SM. Either way, paren-reading and -balancing will stop feeling difficult after a while, so I wouldn't worry much about it -- just pick one or the other and stick with it until parens feel natural.

Josh Horwitz18:03:43

@U077BEWNQ can you give some examples of how to use the sexp-editing commands? I'm not sure how to use them

eggsyntax18:03:16

Oh, for sure! Most of the stuff I'm talking about is what's under SPC k -- I use SPC k s and SPC k b constantly for slurp and barf. Smartparens is built on paredit, IIRC, and this page gives animated examples of most of the capabilities (ignore the exact fn names and key bindings, this is just to give an idea): http://danmidwood.com/content/2014/11/21/animated-paredit.html And the most important one for me is the simplest one, which I do manually -- if I'm going to cut some text in clj/s (or kill it, in emacs terms), I always do it on a per-sexp basis. eg start on the paren at one end, hit v for visual mode, % to go to the matching paren, and then cut/kill. That way cutting/pasting text never results in unbalanced parens.

eggsyntax18:03:51

Oh, wrap and unwrap, that's another two that I use all the time.

feihong22:03:14

awesome, thx for the advice

Russ Olsen16:03:09

@mfikes already said this, but let me try to put it in different words. Macros are functions that you are sticking into the compiler pipeline. When the compiler reads an expression like (do-something some-value), one of the first things it does is look for a macro called do-something. If it finds a macro called do-something, the compiler will evaluate macro just like a function, passing in the code for the arguments - so in the example it will pass in some-value. The result returned by the macro is what the compiler compiles. So a macro is free to generate code that evaluates its argument, munges them somehow or ignores that completely. And if the macro ignores its arguments then they effectively disappear, since the compiler never see them.

Russ Olsen16:03:54

Just to finish the story, if the there isn't a macro called do-something then it's either a built in thing like 'if' and the compiler will generate code for that built in thing or it will generate code to evaluate the argument and call a function called do-something.

brunobraga20:03:43

does anyone know a good clojure boilerplate project for server side? with endpoints and all..Ideally that uses datomic.

Drew Verlee21:03:45

I would take a look at Luminous for something that helps you at least start to wire things together. not sure it has a datomic plugin, but probably does.

val_waeselynck21:03:01

It does, but it treats Datomic like any other «mutable» database, which is a bit suboptimal IMO

samcgardner22:03:31

How do I append a string to a list? cons/conj and friends all seem to append each character in the string individually to the list, which is not what I want to do

eggsyntax22:03:20

@samcgardner (conj ["foo" "bar"] "baz").

samcgardner22:03:07

Yeah, follow-up question. Why does (conj (list "a" "b") "apple") get me [a, b, c, p ...] but working with a vector gives the expected result?

eggsyntax22:03:08

Maybe you were doing (apply conj ["a" "b"] "foo") ?

samcgardner22:03:18

It was caused by using a list rather than av ector

samcgardner22:03:24

But I'm not clear on why this would matter

samcgardner22:03:04

Err, that's an utter lie. Conj works but prepends rather than appends on list

samcgardner22:03:13

I think I was using cons or list* to try and append

eggsyntax22:03:39

Also conj works in a type-specific way, adding elements where they're most efficiently added -- which is different for lists (prepending, since they're linked lists) and vectors (appending).

samcgardner22:03:05

I guess I'll just try and internalise the idea that vectors are my friend

eggsyntax22:03:13

user> (conj '(2 3) 1)
(1 2 3)
user> (conj [1 2] 3)
[1 2 3]

schmee22:03:36

@samcgardner you've got the right idea, usually the only time you deal with lists is when you're doing macros

samcgardner22:03:01

Yeah I didn't realise vectors coped well with appends

eggsyntax22:03:26

It's a really common thing to trip over, it's not just you 🙂

samcgardner22:03:22

Cheers guys ❤️

eggsyntax22:03:54

Note that cons is different, it behaves the same on lists and vectors.

user> (cons 1 [2 3])
(1 2 3)
user> (cons 1 '(2 3))
(1 2 3)

samcgardner22:03:08

If I were hellbent on appending (not prepending) a string to a list, what would I do? (cons string list) ?

samcgardner22:03:48

err, (reverse (cons string (reverse list)) even

sundarj23:03:36

the easiest way is probably (concat '(1 2 3) '("abc"))

brunobraga22:03:01

hey everyone, I am trying to setup a project using datomic, and even tho I have few dependencies I keep getting errors related to conflicts and so when I run lein deps :tree it suggests me to add some excludes, I added them, but it keeps asking me to add mpre. Am I missing some sort of good practice? is it normal to add so many excludes?

Michael Fiano23:03:15

I am a complete newbie, but is this a decent way to shuffle a string? (clojure.string/join (shuffle (seq "abcdefg")))

sundarj23:03:54

that's how i would do it. just searched for how to do it in Java, and doesn't seem there's a better way on that end either

mfikes01:03:56

seq can be omitted in this case and another common idiom is to apply str in lieu of clojure.string/join:

(apply str (shuffle "abcdefg"))

mfikes01:03:24

Oh, sorry, seq is needed in this case.

Michael Fiano01:03:13

@mfikes Thank you. Yeah, I actually switched to apply a little while ago to be a bit more concise and not needing to be delimited

Michael Fiano01:03:48

This is actually the very first thing I ever coded in Clojure, so nice to know