Fork me on GitHub
#beginners
<
2019-12-12
>
alidlorenzo00:12:24

say i have a macro that iterates all words in list and prints them out.. in the snippet below, with the quoted words variable defined outside it works (it prints "hello" and "world)

(def words '[hello world])
(defmacro print-words
     []
     `(do
        [email protected](for [w words]
            (println w))))
but if I change the function such that I pass in that same words as an argument, i.e. (print-words [hello world])` then it doesn't work (it prints "quote" and "[hello world]")

alidlorenzo00:12:50

i'm trying to learn why exactly that is, and how to get the second way (passing the quoted list as a function argument) to behave the same

andy.fingerhut00:12:31

Function calls have their arguments evaluated before the call. Macro invocations do not.

alidlorenzo00:12:40

but what exactly does it mean for '[hello world] to be evaluated before a call, wouldn't it be the same as defining it in an outside scope like I did above?

andy.fingerhut00:12:44

Do you need a macro for this? If you are trying to learn about how macros work as an exercise, no problem, but if you can do what you need with a function, that is recommended.

alidlorenzo00:12:09

oh i simplified the example so I could ask the question

andy.fingerhut00:12:08

The reader reads '[hello world] and returns the data structure that is this list: (quote [hello world]) If you then evaluate that, all expressions of the form (quote x) evaluate to the whatever the value of the expression x is.

andy.fingerhut00:12:42

If you use that as the argument to a macro, the macro code runs without first evaluating that.

andy.fingerhut00:12:40

If you use that as the argument to a function, it is evaluated to the value [hello world] and then the function body executes given that vector value as a parameter.

alidlorenzo00:12:08

regarding third comment, you mean it is evaluated to (quote [hello world]) , right? cause that examples why if i iterate over it prints "quote" and "[hello world"

andy.fingerhut00:12:05

The reader reads '[hello world] and returns (quote [hello world]) . Evaluating that does not result in (quote [hello world]) You can try it at a REPL and see.

andy.fingerhut00:12:33

user=> (read-string "'[hello world]")
(quote [hello world])
user=> (eval (read-string "'[hello world]"))
[hello world]

alidlorenzo00:12:17

ah yea ok, so think I understand now, it's expanded but not evaluated until the macro fires

andy.fingerhut00:12:42

If you replace "expanded" with "read", then I agree.

alidlorenzo00:12:19

got it. it's my first dive into macros so thanks for the help @andy.fingerhut 🙌

andy.fingerhut00:12:40

And it pays to be careful about the times involved in invoking macros. There is macro expansion time, which is during compile time, but that is all before the code returned by the macro is executed/evaluated.

andy.fingerhut00:12:03

I am not clear which of those times you might be referring to by "until the macro fires"

alidlorenzo00:12:53

was referring to when i actually call function in repl and see result, so yea the latter

andy.fingerhut00:12:19

It also pays to distinguish "call function" and "invoke macro" when talking about the behavior here. Not sure which function you mean. Technically, in the implementation, a macro is a function, but it is a function that is passed unevaluated parameters of the macro invocation during compile time, executes during compile time, returns a new expression, and the returned expression is fed back into the compiler (perhaps causing more macros to be expanded), all before the resulting code is evaluated.

👍 8
🤯 4
andy.fingerhut00:12:47

The exploding head syndrome is common, and is one of the reasons to avoid using macros if you do not need them 🙂

alidlorenzo00:12:50

going to have to dig into clojure brave and true again lol

alidlorenzo00:12:05

i'm actually trying to contribute to a library that already uses them (to dynamically generate react elements) so using it as a chance to learn

👍 4
andy.fingerhut00:12:51

Rich Hickey's talk "Clojure for Java programmers" has an interesting introduction to the read time, compile time, where macros fit in, etc. Not sure if it would be helpful, but thought I would mention it: https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/ClojureForJavaProgrammers.md

alidlorenzo00:12:52

will give it a watch, thanks for link

andy.fingerhut00:12:11

When trying things out and debugging, functions like read-string , eval , macroexpand-1 and clojure.walk/macroexpand-all can be useful.

Adrián Rubio Morlote00:12:45

I'm trying to create a REST api with pedestal and datomic... why do I need a deps.edn and a project.clj ??

Adrián Rubio Morlote00:12:04

aren't both for the same thing? (managing dependencies)

andy.fingerhut00:12:28

It is possible to create a project that uses deps.edn, and never uses Leiningen, therefore needs no project.clj file.

andy.fingerhut00:12:58

If you use Leiningen, you need a project.clj file.

Adrián Rubio Morlote01:12:13

so if i want to run the api locally

Adrián Rubio Morlote01:12:21

i should mantain both?

andy.fingerhut01:12:57

You can start a REPL, and/or run the project locally, without using lein. e.g. use clojure or clj commands instead.

noisesmith01:12:05

leinengen does not normally need deps.edn

noisesmith01:12:18

(though it can use it via a plugin - some people prefer that)

Adrián Rubio Morlote01:12:35

is there a reason I should use leiningen for my purposes ?

andy.fingerhut01:12:44

Depends on your purposes 🙂

seancorfield01:12:13

You can most likely do everything you need with clojure/`clj` and deps.edn...

Adrián Rubio Morlote01:12:34

why do people use lein?

seancorfield01:12:43

...but Leiningen is in nearly all the books and tutorials because it's been around the longest. The CLI / deps.edn is relatively new.

andy.fingerhut01:12:20

I believe there are ClojureScript-specific tool environments that pretty much require Leiningen today. If you have Java source in your project, and you want to have a tool to build that for you into .class files, then clj / clojure commands alone will not do that for you.

seancorfield01:12:20

Strictly speaking, you can compile Java with the CLI but it isn't straightforward -- but you don't need other tools 🙂

andy.fingerhut01:12:00

Sure, if you can write a Clojure program to do what you want, you can use clj and clojure commands to run that code for you.

seancorfield01:12:22

No, I mean you can do it with just the -e option 🙂

andy.fingerhut01:12:40

Compile Java source code?

seancorfield01:12:13

Yup. I posted an example somewhere. -e and clojure.java.shell/sh to invoke javac 🙂 🙂 🙂

andy.fingerhut01:12:30

Sure, you wrote Clojure code to do what you want, as I said 🙂

seancorfield01:12:57

But not a separate program: just a single clojure CLI invocation. I think that's important.

seancorfield01:12:21

When we started with Clojure back in 2010/2011, Leiningen was the only choice. We switched from Leiningen to Boot in 2015 and from Boot to CLI/`deps.edn` about a year ago.

Adrián Rubio Morlote01:12:05

Ohhh nice! Thank you guys!

Adrián Rubio Morlote01:12:30

This slack is amazing... I was having a hard time finding a lot of information until I discovered it

seancorfield01:12:41

If you're just getting started with Clojure in 2019, I'd suggest trying to use the CLI/`deps.edn` and learn all the ins and outs by reading the docs on http://clojure.org and asking questions here.

👍 8
seancorfield01:12:45

If you do go down the CLI/`deps.edn` path, take a look at https://github.com/seancorfield/dot-clojure as a way to jumpstart your experience with it.

didibus02:12:41

To be fair, you can't do everything with clj

didibus02:12:15

You can only manage dependencies with it

didibus02:12:46

You need other tools for what's missing which you can hook up to clj, kinda like extending it with additional plugins

didibus02:12:58

Where as lein has a lot of included build like features. Though even with lein, there are some things when you need additional plugins for

didibus02:12:41

But in general lein has one and only one way to do things, and a set of common additions

didibus02:12:05

Clj is more of a wild west of additions currently

didibus02:12:26

For better or worse. Its unclear which one would be easiest to pick up, probably depends on what you're doing exactly

seancorfield02:12:02

I think lein is "easier" but clj is "simpler" 🙂

Hojat04:12:34

Hi every one I've been a JavaScript programmers for more than 10 years and in my journey to functional programming with JavaScript I was introduced with clojure and I'm really interested The thing is I just can't start coding with clojure, I find myself reading and reading and ... watching talks about clojure I just can't start Any idea what should I do?

seancorfield04:12:18

Have you tried the Clojure Koans?

seancorfield04:12:48

That's a nice, gentle hands-on approach to writing little bits of code in narrative.

didibus04:12:31

That's a good one. Try http://4clojure.com

Hojat05:12:09

I haven't tried them yet but I will as soon as get home thank you

seancorfield05:12:43

@hojatjafari What books / tutorials have you read through so far? Have you read Brave and True?

Hojat05:12:07

And which book should I read? After Koans/4Clojure or before it?

Hojat05:12:40

I tried few chapters of brave and true but I got lost

Hojat05:12:35

It's not hard, I just can't get the big picture

Hojat05:12:23

That might be great if there was a tutorial that walks you trough implementation meaningful things and teaching new things as you use it in context of what you're building

seancorfield05:12:24

Hmm, Living Clojure might be worth a try by Carin Meier. If you don't mind actually buying a book.

Hojat05:12:14

I'm watching the context menu, it seems cool

Hojat05:12:37

I'll give it a shot Ty @seancorfield

Hojat05:12:58

I've seen you helping ppl alot

RollACaster07:12:30

Hi, I have a number of events (e.g. 4 events) and want to distribute them evenly within a week, how would you approach this problem? I am a JS-Developer and I would use https://github.com/d3/d3-scale for that. Is there sth similar in clj or do I need to write my own logic?

didibus07:12:45

Hum... well you could use d3-scale on cljs

didibus07:12:28

There's most likely a Java library that can do similar things as well

👍 4
Space-Otter12:12:29

Hey guys, I need to write some predicate functions. What will be the most idiomatic way to write predicate functions? For example:

(defn student-id-v1?
  [id]
  (re-matches #"[a-z]{2}[0-9]{6}" id))
;; if match: return match => true
;; else: return nil => false

(defn student-id-v2?
  [id]
  (if (re-matches #"[a-z]{2}[0-9]{6}" id)
    true
    false))

kwrooijen13:12:27

I’d do something like this..

(def student-id-v1?
  (comp some?
        (partial re-matches #"[a-z]{2}[0-9]{6}")))

👍 4
Space-Otter13:12:45

Thank you for the suggestion ☺️

kwrooijen13:12:28

You’re welcome 🙂

Pavel Klavík18:12:02

@UR5QE2DUZ The overall approach is to return a value or nil instead of returning true or false. The former works the same in tests but it is more useful in other places as well.

Space-Otter19:12:15

@UFHE11C83 I see. So I can also use such function like this in spec, right?

(defn student-id-v1?
  [id]
  (re-matches #"[a-z]{2}[0-9]{6}" id))

Pavel Klavík22:12:48

Ya, i think this is simplest and most understandable.

kwrooijen09:12:22

This wouldn’t really be conforming the style guide though: https://github.com/bbatsov/clojure-style-guide#pred-with-question-mark It states that if a function ends with a question mark, it should always return a boolean. This is also common practice in Ruby. Even though nil and false are both falsy values, they aren’t really the same.

Space-Otter12:12:33

I found this discussion under the ticket https://clojure.atlassian.net/browse/CLJ-2141 @UG9U7TPDZ Is there any advantage to use a transducer like in your example over something like that?

(defn student-id-v1?
  [id]
  (comp some?
        (partial re-matches #"[a-z]{2}[0-9]{6}")) id)

kwrooijen12:12:51

Not sure what you mean by that? You mean using defn vs def + comp? My example isn’t a transducer. It’s just a value that composes. I personally find it much simpler, and if you want to show the reader what the input is (id) you could write a spec (which is recommended). And just for clarity, this as a transducer would look like:

;; The transducer
(def xform
  (comp (map (partial re-matches #"[a-z]{2}[0-9]{6}"))
        (map some?)))

(transduce xform conj ["invalid-id" "ab123456" "another-invalid-id"]) ;; => [false true false]
Notice that the functions are reversed, and wrapped in map (because a transducers acts on a stream of data) Anway this question wasn’t really about transducers but I thought I’d try to at least clarify 🙂

kwrooijen13:12:31

some? returns false if the value is nil, otherwise it returns true

evocatus13:12:42

can you help me with walkable library? I managed to get a "floor-plan" that is created correctly. I wrote a simple query:

(def my-query [{(:person/all {:limit 5}) [:person/name :person/surname]}])
When I try to print it I get
[{nil [:person/name :person/surname]}]
What should I do to get a query string to pas it to jdbc/query? Or does it work other way? I suppose there should be some function for that in walkable library but there is nothing about executing queries in it's docs

noisesmith18:12:36

keywords are not magic, when you use () to run them as functions, they look themselves up. Looking up :person/name in that map returns nil, as it should

Chase15:12:58

I'm curious on how you folks approach understanding a function's arguments. If you see something like (defn foo [user]) or [name] or just [input] how do you begin parsing that? I think that's the one thing holding me back the most by reading other's clojure code. I try other languages and always run back to clojure but the one thing I miss from statically typed languages is an explicit function signature with inputs and outputs.

kwrooijen15:12:13

@chase-lambert I think it really depends on who writes the code. I agree though, that’s probably the thing I miss the most in Clojure. When writing Haskell / Elm it’s really clear what goes in and comes out. Clojure has spec which helps a bit in this case, but it’s not a de-facto standard to use it. Also it’s a lot more clutter than a simple type signature (specs are much more expressive, but it hurts readability). I think what goes a long way is naming conventions for functions. user->id Convert (`->`) a user to id user-activated? Check if a user is activated, MUST return a boolean (`?`) set-user-name! Modify user name state (`!`) (for example an atom) It’s clear that these functions convert, check, or modify users. But what a user is, is up to the application. Having simple functions like this does help (In the style guide it’s suggested 5-10 LOC https://github.com/bbatsov/clojure-style-guide#function-length). If a user is specced in the namespace, it’s quite clear what defines a user.

Chase15:12:27

This does seem to be what I've gathered most people are doing. I was wondering if something like this:

(defn power                                                                  
  "exponent calculation. takes two numbers, returns a number"                
  [base exp]                                                                 
  (reduce * (repeat exp base)))
would help at all as a quick example. Or maybe other clojurists would hate that. Just use the doc string. But whenever I try to put something like a map in there to show the shape of expected input I think the compiler or repl doesn't like it because it's trying to read it maybe?

yuhan15:12:01

I often put a (comment ...) form directly below a function definition if it's particularly confusing and eval-to-comment on sample data, just as a reminder of the input and output shapes

kwrooijen15:12:46

IMHO, if you start writing in the comments what the arguments / return values are, then just write a spec. Then you actually have a testable case, which will fail if the input / output changes. A docstring can become outdated if not maintained

👍 8
Chase15:12:09

That is a good idea. I know many people have that comment form at the bottom of their source files for stuff like that too. What about actual type annotations? I know for performance reasons you sometimes have to do (defn foo [^String s]) but can you do that for things like maps or other non primitive types?

Chase15:12:31

That makes sense. I've been meaning to start exploring spec finally anyways

kwrooijen15:12:54

Spec is a great tool for any Clojurian

Chase15:12:34

I do like pre and post conditions like your style guide link mentioned too. Do you think that would frustrate maintainability too?

kwrooijen15:12:00

Also, slightly on topic, when it comes to function name / doc / body Function name: WHAT does the function do (e.g. user->id converts a user to id) Function body: HOW does it do it? 5-10 LOC shouldn’t be too difficult to understand how something is done Function doc: WHY is it doing this? Maybe there’s a specific business case for this function, to prevent a bug, performance optimization, etc Though this standard is not really a clojure thing, it’s what I try to use as a guideline. It’s mostly just preference

kwrooijen15:12:56

I don’t think that’s really a bad thing. If the data going through isn’t conforming the spec, might as well fail (it failed somewhere already anyway)

kwrooijen15:12:02

But that depends on the application

Chase15:12:02

For "function signature" purposes how would you see spec vs pre/post conditions in the function itself? When reading clojure code I don't think I see too much pre/post conditions or anything throwing exceptions (I am not confident in error handling at all tbh) for that matter. I don't run across spec either though. Maybe most just don't have a problem parsing expected input/output

kwrooijen15:12:47

I think specs are the better choice. Spec has a nice toolset for checking what’s wrong. For example if the user map is missing a key, spec can help you with finding out exactly what it is. So it helps with understanding error messages. I’m not sure pre/post really shines in this area. (I don’t really use pre/post much myself)

Chase15:12:44

Sounds good. I've been very curious about it anyways. I always wonder if I will gravitate to static typing mostly because I just don't seem to advance much in Clojure (or programming in general, but this is one of my excuses) yet I just don't enjoy other languages as much even if I can understand what's going on better. Maybe spec will let me get the best of both worlds.

kwrooijen15:12:43

Also I think spec would be a better choice since it doesn’t directly influence your code. pre will crash your function call if incorrect data is supplied. If you want that to happen then it’s fine.

Chase16:12:56

I see others mention often that they just like to use spec at the "boundaries" so I do want to keep exploring other techniques to understand more what the functions are expecting and doing because I might not have it available when reading other's code. Thanks for the chat!

rschmukler17:12:22

Hey all! I think I may have found a bug in clojure.... Can anyone explain why this loop terminates?

(let [xs (map volatile! [1 2 3])]
    (loop [seen? #{}]
      (let [hash (map (comp inc deref) xs)]
        (if (seen? hash)
          hash
          (do (doseq [x xs]
                (vswap! x inc))
              (recur (conj seen? hash)))))))

rschmukler17:12:08

I noticed that it specifically requires xs and hash to be lazy sequences - which seemingly causes all set members to become the same.

rschmukler17:12:26

Is it because the map on xs doesn't cache values? So then the xs are actually different each recursion of the loop?

rschmukler17:12:17

Hmm actually it looks like:

(let [xs (into [] (map volatile!) [1 2 3])]
    (loop [seen? #{}]
      (let [hash (map (comp inc deref) xs)]
        (if (seen? hash)
          hash
          (do (doseq [x xs]
                (vswap! x inc))
              (recur (conj seen? hash)))))))
terminates

rschmukler17:12:40

So it's specifically hash being lazy. Collecting it into a vector or something causes it to never terminate

hiredman17:12:40

don't use volatile!

rschmukler17:12:52

I know... same behavior with atoms too

hiredman17:12:44

don't use mutation

rschmukler17:12:58

That's not very helpful...

andy.fingerhut17:12:01

What are you trying to achieve?

rschmukler17:12:14

I get that I can solve it without mutation

rschmukler17:12:24

That's not the problem

hiredman17:12:25

your bug is because you are reading things lazily, and mutating them

hiredman17:12:58

so when the lazy reading will get different values depending on if it is forced before or after the mutation

hiredman17:12:59

at the very least limit your mutation, wrap the source collection in a single mutable reference instead of a mutable reference per element

hiredman17:12:19

it is at least harder to run into that error if you do that

rschmukler17:12:27

I guess I'd potentially expect invoking conj onto a set to check whether something had been realized? and if not, invoke it

hiredman17:12:52

switching your initial collection to a vector like you did sort of looks like it fixes things, but horrifyingly will break things once you exceed 32 elements

hiredman18:12:00

realized? is bad

hiredman18:12:17

if you write code that uses it you are almost guaranteed to have bugs

rschmukler18:12:54

I understand that these aren't idiomatic ways of solving things. If this is the intended language behavior, then that's one thing - but I somehow can't imagine the utility in having sets not realize the values of lazy sequences on conj or their IFn invokation.

hiredman18:12:20

the point at which you conj it is already too late

hiredman18:12:28

because you alter the value of the cells before the conj

hiredman18:12:26

so the values you are conj in, are not what the values would be if the empty set actually had to traverse the lazy seq to determine if its contents where contained in it, which it does not

rschmukler18:12:46

That makes sense

didibus18:12:01

Conj doesn't add the elements of the lazy-seq into the set by the way

didibus18:12:10

It adds the lazy-seq to the set

hiredman18:12:14

the reason switching to a vector sort of seemed to fix things is switching to a vector got you a chunked seq, in which case map is only lazy in chunks of 32

didibus18:12:19

Which doesn't require reading its elements

andy.fingerhut18:12:20

Putting mutable objects inside of immutable collections is kind of a red flag here, too, isn't it?

12
rschmukler18:12:41

(on the hash, which makes sense, because it caches its values)

andy.fingerhut18:12:52

Oh, I guess the set isn't getting mutable objects put inside of it, never mind.

rschmukler18:12:52

This bug makes sense now, and is indeed not a bug in clojure at all. Damn, thought I got one. Thanks for the help @hiredman

recholsknauber18:12:49

I cannot for the life of me get Clojure code blocks working in Org Mode

recholsknauber18:12:50

Looks like the main issue is a code block can't connect to a running cider nrepl

papachan20:12:49

maybe a question for #emacs channel?

👍 4