Fork me on GitHub
#beginners
<
2020-09-04
>
Malik Kennedy00:09:56

(defn drop-both-sides
  [n lst]
  (drop n (drop-last n list)))
I expect this to do what the name says, take n elements off beginning and end of list. Instead I get error about IllegalArgumentException Don't know how to create ISeq from: clojure.lang.PersistentList$Primordial. How would I do a double take?

Alex Miller (Clojure team)00:09:04

you have lst in the arg and list in the code

Alex Miller (Clojure team)00:09:09

list is a function

❤️ 3
Gustavo Bertolino01:09:46

Hi guys, I took part of a good discussion yesterday about the requirements a language should have to be considered as part of the Lisp family. In this case, they argued that LFE is not effectively Lisp, but just a language with syntax similar to Lisp, because for instance it doesn't have homoiconicity (I'm not sure about this statement). Why is it this exactly? The same rationale would apply to Clojure?

dpsutton01:09:24

Ask in #off-topic These discussions can be pretty abstract and not good beginner fodder

👀 3
Joel02:09:47

should i bother with namespaced keywords? I'm guessing the answer is "it depends", but then i'm wondering what it depends on 😞

seancorfield03:09:13

If you're starting a project from scratch, I would make an effort to use them and get used to them.

seancorfield03:09:28

Sure, you have to map them to/from unqualified keywords for your API (either REST/like input and results in JSON or HTML forms and URL query params), but that's to be expected -- and no reason to avoid them inside your code.

seancorfield03:09:04

If you're doing JDBC stuff, next.jdbc's default is qualified keywords. Datomic uses qualified keywords. Spec is really all about qualified keywords.

seancorfield03:09:57

They have a lot of semantic benefits, e.g., a :person/name is different to a :product/name, after all.

Joel03:09:30

for ones with namespaces, it seems I don't actually need to declare them somehow, right? I just pretend they exist?

dpsutton03:09:06

i agree about the pain of namespaces for things that hit the wire or go into a database. but for internal stuff it makes it quite nice to disambiguate keywords and usage. For instance, our backend sends a websocket notification message with topic :report/progress and :report/complete. (over the wire but we use edn so it works for us). Finding the emitters and consumers of these notifications is quite easy. if they were just :progress and :complete there could be hundreds of instances of these keywords. But i can quickly find these particular notions of progress and complete. His argument that "report_progress" and "report_complete" can go in a db without change is true but these are ephemeral data in motion so we don't have that issue

seancorfield03:09:33

> things that hit the wire or go into a database There's no problem with them going into the database: the qualifier is simply ignored (by both clojure.java.jdbc and next.jdbc).

seancorfield03:09:14

With c.j.j you can get qualified keywords on things coming out of the database, but it's primitive: you can specify a simple :qualifier "prefix" and that's it. With next.jdbc, it tries hard to put "sensible" qualifiers on the keywords, matching the table the column comes from (very useful in a joined query).

seancorfield03:09:08

It won't surprise you that I disagree with a lot of that article 🙂

seancorfield03:09:24

If our front end was ClojureScript, rather than JS, we'd probably traffic in EDN or use Transit so we could keep qualified keywords in play right from the user interface all the down to the database and back again.

dpsutton03:09:58

your comments let me know of this "surprise" 🙂

seancorfield03:09:20

You should have seen the dust up on Twitter about it 😆

😆 3
dpsutton03:09:45

surprising. its normally nuanced and sincere 🙂

Joel04:09:16

along similar lines.. if you are dealing with Java objects do you sort of treat those as "over-the-wire" and turn into a common format, or just use them "as is" in your clojure code... unless of course all your code is clojure in which case moot question.

Gargarismo16:09:43

All clojure objects are equally as enfranchised as java objects

Gargarismo16:09:11

In the sense that they have an innately compatible representation as jvm bytecode

Gargarismo16:09:53

There are quite a few libraries that wrap Java libraries to make them "clojure-y"-- that's mostly for syntactical preference-- but there's no reason not to use Java objects "as is", as it is pretty straightforward.

seancorfield04:09:10

@joel380 Since that blog post doesn't support comments (which seems to be an unfortunate trend on blogs, these days, so you can only take the posts on face value, without the benefit of an actual discussion), it might help you to read this thread about it https://clojureverse.org/t/clojures-keyword-namespacing-convention-considered-harmful/6169

seancorfield04:09:39

Oh, now the comments are loading on the blog. They wouldn't load earlier so I thought there were no comments there. I know the discussion happened in multiple places.

seancorfield04:09:49

Re: Java objects -- can you provide a bit more context @joel380?

seancorfield04:09:03

Clojure is a hosted language that expects you to use Java interop. Not sure what "over-the-wire" would mean in that context: it's very common for Clojure code to manipulate Java objects.

Cas Shun16:09:48

I have an edn file with a map, I would like to retain the written order of that map. Is there a way to accomplish this?

Matthew Cheney16:09:00

You should not rely on the order of maps. If you need to represent order, use a list or vector.

3
delaguardo16:09:58

1. create input stream from this file. 2. read until you hit \{ character 3. start reading the same stream via clojure.tools.reader.edn/read until you get “Unmatched delimiter }.” exception 4. already read items should have even number of items represent kvs of the map in file, just turn them into desired structure

Alex Miller (Clojure team)17:09:02

in short, no. use an ordered coll like a vector.

Ross Chapman17:09:22

Hey all. I’m just starting out with Clojure and coming from a JS background. I’m having a bit of trouble understanding variable scope. Here’s an example from a Udemy course where a validCoupon variable is defined in a function isCodeValid and then used inside another function called getCarPrices. You can’t do this in JS because function scope boundaries hide internals from each other. So what is happening when validCoupon is assigned? Is that variable now available on a “global” scope relative to the namespace (ie file)?

Alex Miller (Clojure team)17:09:06

This is bad code and you should take a different course :)

12
💯 15
Alex Miller (Clojure team)17:09:42

def creates a global var and should only be used at the top level of your code

Ross Chapman20:09:08

Is there a reason that Clojure permits creation of global variables in function scope like that?

Ross Chapman20:09:33

I’m sure there’s some use case I haven’t thought of but I’m not used to this being possible.

Alex Miller (Clojure team)17:09:48

Inside a function you should use let to create lexically scoped locals

🙌 3
Ross Chapman17:09:48

@alexmiller Ah that makes a ton more sense. Actually, I’m realizing a big issue with this course is that the instructor doesn’t introduce scope at all. The section on Variables talks only about immutability, valid characters, and case sensitivity but not scope.

ghadi17:09:29

yeah even the examples in the preview video are totally wrong

ghadi17:09:38

highly anti-recommend

Ross Chapman17:09:53

you mean “wrong” like not conventional/anti-patterns etc… ?

ghadi17:09:06

you never nest a def in a defn, ever

Alex Miller (Clojure team)17:09:30

If you have some place to leave feedback for this, please do :)

Drew Verlee21:09:50

@rosschapman Here as an intro to clojure i wrote a while back. https://drewverlee.github.io/posts-output/2017-4-26-simple-by-design Beyond that. Purely functional TV and Lambda island has online classes if you like that medium

💯 3
athomasoriginal13:09:00

Indeed. If your learning Clojure(Script) and you like video courses: • lambdaisland • purelyfunctionaltv • https://www.learnreagent.com/

Matthew Cheney17:09:01

@rosschapman Have you seen this book? It's a good place to get started: https://www.braveclojure.com/foreword/

🙌 3
👍 3
Ross Chapman17:09:57

I haven’t! My interested was piqued after reading Zachary Tellman’s https://elementsofclojure.com/. Which I liked for the parts that were not Clojure specific.

Ross Chapman17:09:13

Also generally wanting to try something more Lisp-y.

Ross Chapman17:09:00

I’ve also been reading a lot lately so thought I’d try learning from online video/tutorials just to mix things up.

Matthew Cheney17:09:02

Makes sense. If you want to practice small problems, I'd recommend HackerRank. It really helped me, especially when I was itching to actually start coding (rather than just read): https://www.hackerrank.com/domains/fp

Ross Chapman17:09:53

Oh cool. I noticed Code Wars also supports Clojure. It’s really great to see other folks’ solutions to learn the syntax.

dharrigan18:09:44

some excellent resources there and lots of learning to be had!

Ross Chapman19:09:36

Already watching a talk now. Thanks for the rec!

practicalli-johnny11:09:00

https://exercism.io/ has a clojure track and you get a mentor too. You can also use the #code-reviews channel on the Clojurians slack comunity to get feedback

practicalli-johnny11:09:22

A really good way to practice using the core functions of Clojure is http://www.4clojure.com/

practicalli-johnny11:09:50

And as you are from a JavaScript background, https://www.learnreagent.com/ should be of interest (the author of the course is predominantly a JS developer professionally)

Grant Isom18:09:02

dev=> (car/wcar {:host "localhost" :port "6379"})
Execution error (ConnectException) at  (Net.java:-2).
Connection refused

Grant Isom18:09:17

Anyone ever have issues connecting to redis from the repl?

Joel18:09:53

For us folks trapped in json-land... Is writing clojure spec directly against the incoming json data model and generating json schema for clients from the clojure.spec effective? I'm hoping that spec can be leveraged somehow for transforming directly into clojure data down the road (whereas for now we likely use existing Java objects).

seancorfield19:09:10

Well, you'd have to convert from JSON to keyword-based hash maps before you could apply Spec, so you could either use :req-un/`:opt-un` on plain keywords (and later convert to whatever internal qualified-keyword domain structures you wanted) or perhaps arrange for the JSON-to-map conversion to add qualifiers and use regular Spec stuff (`:req`/`:opt`). But in Spec 2, handling of unqualified and qualified keywords diverges further due to s/schema/`s/select` effectively replacing s/keys.

Ross Chapman19:09:45

This code leads me to believe that clojure = does some kind of structural equality check between lists? Is that correct?

(= (list ()) (list ()))

Eddie19:09:15

I think you are correct. The Clojure collections are values, and thus we don't have to compare by references. About halfway down the "Summary" section of this page explains how = works with collections. https://clojure.org/guides/equality

thanks3 3
Ross Chapman20:09:52

Yep: “Sequential collections (sequences, vectors, lists, and queues) with equal elements in the same order are equal”

Ross Chapman20:09:07

So it’s structure and value comparison

athomasoriginal14:09:22

@rosschapman it’s not as much a thing in JS, but if you’re ever curious what Clojure code is doing (in the event that the docstring isn’t enough) the source is available. In this case: https://github.com/clojure/clojure/blob/clojure-1.10.1/src/clj/clojure/core.clj#L783 I found this increasingly helpful as I was learning the language 🙂

Ross Chapman19:09:43

If you mean by “not as much a thing” as in JS doesn’t have simple means of comparing objects and arrays (ie collections) you’re definitely right! Even with === Strict Equality Comparison it will only return true by reference. So getting “content” or structural comparison is trickier. So it looks like = implements clojure.lang.Util/equiv under the hood which is referenced here (I think?): https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Util.java#L24. Thereby implementing both Java’s == and equals() check. And I’m assuming comparing lists falls through to line 33 where equals() is called because the == check on lin 25 fails for checking by reference, and then the other conditions are more obviously un-satisfied. Thanks for the tip!

Drew Verlee19:09:08

Clojure's = does a nested value check across types with similar properties. So yes.

Ross Chapman20:09:58

what do you mean by “nested” check? like recursive?

Drew Verlee21:09:24

(= [[1]] [[1]]) ;; => true (= [{:a :b}] [{:a :b}]) ;; => true recurisve would have been a better word.

phronmophobic20:09:19

> (= ()
     (for [x ()] nil)
     []
     (list)
     (map identity []))
true

Matthew Cheney20:09:30

On a similar note, I usually use (frequencies) to compare 2 maps (such as (= (frequencies map-one) (frequencies map-two)) ). Is there a better way to check map equality, or is that the de-facto solution?

phronmophobic20:09:49

you should be able to do (= map-one map-two). are you seeing unexpected results?

Matthew Cheney20:09:46

Sorry, I mis-typed. I mean to compare lists independent of order. I've been comparing lots of lists of maps, which is why I remembered wrong.

phronmophobic20:09:46

> (= {:a 1}
     (hash-map :a 1))
true

phronmophobic20:09:24

comparing frequencies seems reasonable for comparing seqs in an order independent way. depending on the situation, it might also be a hint that you're using the wrong data structure

nando21:09:13

In Sean Corfield's usermanager example, compojure handlers are specified with a leading #' . The app has a model view controller architecture, and within the controller, calls to (assoc-in request ...) are used "add" data into the request map and pass it into the views. I find the same thing in a similar example using reitit, for example ["/list" {:handler #'user-ctl/get-users}]

hiredman21:09:44

#'
is called var quote, and it gives the var that a name refers to instead of the value of the var the name refers to

nando21:09:25

In my experimentation with building an MVC app using reitit, I've found that (assoc-in request ...) does not work unless I specify the handlers with a leading #` . I also seem to be having issues passing form params into the controller for processing. In a functional sense, what is this doing in this case?

hiredman21:09:28

user=> (def x 1)
#'user/x
user=> x
1
user=> #'x
#'user/x
user=> (deref #'x)
1
user=> @#'x
1
user=>

nando21:09:05

So it is passing a var rather than evaluating the handler immediately?

hiredman21:09:31

not exactly

hiredman21:09:48

a var is its own thing, and it evaluates to itself

hiredman21:09:11

a var is the little mutable cell top level definitions are stored in

hiredman21:09:39

which is why if you define a function f, then define a function g which uses f, then redefine f, g will use the new definition

hiredman21:09:25

g refers to f through the mutable var, and if you redefine f it just mutates the var

hiredman21:09:22

vars have a useful feature that they can be invoked like functions, and when you invoke like functions they just invoke whatever value they have with whatever arguments where passed

hiredman21:09:41

user=> (+ 1 2)
3
user=> (#'+ 1 2)
3
user=>

hiredman21:09:21

passing vars instead of functions allows you to update by defining the vars, and have the server pickup the new behavior without being restarted

nando21:09:34

Ok, I see this behavior in the routing libraries.

nando21:09:39

Thanks for the higher level explanation @hiredman

Nassin21:09:03

What makes this behavior different when running the server? in pure clojure function g implicitly uses the var's new definition for f, by just using f's name, but with the server you need to pass the var directly

seancorfield22:09:29

@kaxaw75836 It's not about "server" vs "pure clojure" -- it's about contexts where the (current) value of the function is captured and therefore new definitions of it are not seen.

seancorfield22:09:27

Here's a REPL session that shows where this comes into play:

user=> (defn f [g] (fn [coll] (map g coll)))
#'user/f
user=> (defn q [n] (inc n))
#'user/q
user=> (def ff (f q))
#'user/ff
user=> (ff (range 5))
(1 2 3 4 5)
user=> (defn q [n] (dec n))
#'user/q
user=> (ff (range 5))
(1 2 3 4 5)
user=> (defn q [n] (inc n))
#'user/q
user=> (def ff (f #'q))
#'user/ff
user=> (ff (range 5))
(1 2 3 4 5)
user=> (defn q [n] (dec n))
#'user/q
user=> (ff (range 5))
(-1 0 1 2 3)
user=> 

👍 3
seancorfield22:09:49

Note how the first redefinition of q is not picked up by ff -- we still get the inc-based version -- but in the second section, where ff was def'd to use a Var reference #'q, the redefinition of q (to use dec) is picked up by ff in that last call.

Drew Verlee14:09:34

When is it necessary too pass a var reference? It seems like you could either pass the var or use dynamic binding. Both of which I see more often and so I would argue are more clear to me.

seancorfield14:09:09

The dynamic binding is likely to be less convenient in this case (handlers and middleware), both in the REPL and in use in the program. It can be a good fit for a call-tree-specific value for a "global" though.

Drew Verlee19:09:36

The issue is communication. Passing the #' like that means the caller has no chance to trace back where that change happened in the code. Not in anyway i know of short of reading every expression read in. I would use the dynamic var to represent the default case then rebinding when the default wasn't sufficient:

(defn ^:dynamic *q* [h] (dec h))

(defn f
  ([coll] (map *q* coll))
  ([coll q]
   (binding [*q* q]
     (map *q* coll))))

(f (range 5))
;; => (-1 0 1 2 3)

(f (range 5) inc)
;; => (1 2 3 4 5)
This was a pattern discussed in elements of clojure which i had to reference. under "no one should have to know you used binding". That's not a call to authority, its more that if you happen to have the book he walks through his reasoning in some detail so it might provide more context here then i am.

seancorfield19:09:31

That's not the same issue we're discussing here tho'. Using #' is specifically about development and REPL usage, so you can update the function definition, eval the top-level form, and have the changes picked up without restarting your server process.

seancorfield19:09:17

#' isn't about changes "happening in the code" -- it's about changes via the REPL.

seancorfield19:09:32

If you're in a situation where a dynamic Var makes sense then, sure, EoC is good advice to "hide" the binding form. That's great for default behavior that you might want to override further up the call chain.

Drew Verlee19:09:51

Is there something you can do with the reader macro that you can't do with the dynamic binding? I'm not seeing it if so. It just seems like a matter of convenancy. passing the reader macro means that the change isn't caught by a lexical scope. I'm argueing that makes it harder to follow back to see why things changed. (defn g [coll] (f coll)) ;; top level rebind / restart the server: (binding [q inc] (g (range 5))) ;; => (1 2 3 4 5)

Drew Verlee19:09:14

i guess in terms of searching it would be (ff (range 5)) and then i would look up (def ff (f #'q)) and (def ff (f q)) so the downside is that i might miss that it was redefined.

Drew Verlee19:09:01

err. it would be that i have no idea which of those two were evaled last?

seancorfield20:09:20

You seem to be completely missing the point of this discussion...

seancorfield20:09:38

binding is not a good solution to writing REPL-friendly code.

Drew Verlee21:09:44

Ah. I see, we have to alter the in the global context because thats the only avenue to the "running" repl.

seancorfield22:09:45

Right. It's why you'll see a combination of Var-quote references and also some regular global Vars that are manipulated via alter-var-root -- neither of which make sense as a "general" programming technique but are useful in a REPL development context. We use Component heavily at work and our normal usage is something like

(defn -main [& args]
  (let [app (component/start (build-system (parse args)))]
    (process app)
    (component/stop app)
  (shutdown-agents))
but in the REPL we'd use stuff like this
(defonce ^:private sys (build-system {:some "stuff"}))
...
(alter-var-root #'sys component/start)
...
(process sys)
...
(alter-var-root #'sys component/stop)

seancorfield22:09:09

(all in a Rich Comment Form)

seancorfield22:09:01

Some of our apps that we tend to run Socket REPLs in have an extra line in -main that does (alter-var-toot #'sys (constantly app)) once the component is started, before the process call. That way we can connect into the running process and get access to the core application system via a global at the REPL.

seancorfield22:09:29

This is essentially the pattern used in middleware and (some) routing definitions in web apps in Clojure, which is why it matters.

Nassin22:09:14

got it! 👍

nando23:09:55

@seancorfield Thanks for the explanation. It's a subtle but essential point that isn't highlighted in router docs or simple examples. Very thankfully, changing my router handlers to use var references solved all the mysterious issues I was having. Suddenly, data is persisting properly to a datomic dev-local instance from a form and I have a list of that data displaying in my app! 😀

🚀 6