This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-12-03
Channels
- # adventofcode (91)
- # announcements (7)
- # aws (3)
- # babashka (69)
- # beginners (46)
- # calva (30)
- # cider (12)
- # clj-kondo (88)
- # cljs-dev (11)
- # cljsrn (1)
- # clojure (195)
- # clojure-dev (21)
- # clojure-europe (2)
- # clojure-italy (13)
- # clojure-nl (56)
- # clojure-spec (4)
- # clojure-sweden (6)
- # clojure-uk (27)
- # clojurescript (179)
- # core-async (2)
- # cryogen (1)
- # cursive (2)
- # data-science (1)
- # datomic (57)
- # fulcro (15)
- # graalvm (9)
- # instaparse (6)
- # joker (18)
- # juxt (9)
- # leiningen (6)
- # off-topic (20)
- # other-languages (10)
- # pathom (5)
- # re-frame (20)
- # reitit (2)
- # rewrite-clj (5)
- # shadow-cljs (78)
- # sql (34)
- # tools-deps (128)
- # uncomplicate (16)
- # vim (6)
I’m taking up the habit of writing a “tip of the day” on our office’s whiteboard. We have a lot of juniors, so elementary tips are most welcome. What are your “tip of the day” topics you would’ve liked to see when you were starting out?
with-open
and lazy sequences do not mix 🙂
definitely! someone actually ran into that last week.
avoid side effects in lazy sequences is a good tip of the day for us
Isn't the with-open
issue not just about side effects, but more generally that a lazy sequence can't be returned out of the middle of a binding block, when materializing the sequence will require those bindings?
But, bindings have a similar effect, in that the bound variable is released before the lazy sequence had a chance to use it
That's only true of you capture the binding, but if you capture the bound value you're good
Yes, you're right, that's what I was trying to say.
Both of them create a scope, outside of which you'd better not reference the bound vars, and lazy sequences make it easy to make that mistake.
But still, the end result is that lazy sequences shouldn't be used with side effects. That means they shouldn't use any global var, be them dynamic or not. And they shouldn't use a file either or anything else that's a side effect
If they're realized before exiting the scope, why not?
I still think the problem is when they escape the scope.
Well, carefully using them sure. But, what you're effectively describing is using lazy sequence eagerly :p
It's fine to wrap a doall around a lazy-seq too, assuming you're careful that nested lazy seqs are also wrapped and all that
Yes, that's the kind of thing I was referring to.
But, all I can say is, when I was a more beginner Clojure dev, I'd use a ton of doall, and now I don't think I used a doall in the last year
Nothing wrong with them, but I guess I just got the instinct to use other things instead
I understand, thanks.
I guess I also just changed the way I code in general as to minimize and isolate most side effect, so that probably contributes to me not using those as much
Recently I've been using run!
as he recommends. But, for example, sometimes I use filter
on the source collection, which returns a lazy seq. So I am using a mixture of lazy and strict, which doesn't seem all that bad.
No, the source is just data but may be a lazy seq. My point is just that it is not specific enough, and could be confusing to beginners, to say that you can't mix side effects with lazy sequences. I think what would be useful is a more specific description of what to avoid. For example, if we could define exactly what you mean by "side effecting collection", in terms a beginner could understand, that might help. But I think that is actually not so easy to understand. IMO even beginners need to read about and understand laziness and its pros and cons, as described in Stuart's page for example. A one liner that just says "don't mix them" I think isn't enough.
Or at least, it wasn't enough for me as a beginner.
Maybe I'm wrong, maybe it is enough to say: Don't use any function that produces a lazy sequence to perform side effects. For example, functions passed to map
should not perform side effects. However, lazy sequences may be used as inputs to functions such as run!
that do not produce a lazy sequence themselves, and the function passed to run!
may perform side effects.
Almost one line, if that is really enough as a rule of thumb.
I can see how even understanding what a side effect is can be challenging to a beginner at first
Ya, you may be right. Don't do any side effects inside the function passed to the sequence fns such as map, reduce, filter, remove, etc.
That might be clearer. Where a side effect is making use of any global variable, of any shared mutable container, data-structure, object or binding, and of any IO operation.
Yeah, that seems pretty clear.
Seems like anything that does bindings is a little dangerous and requires special care.
Using lazy sequences inside the block seems fine, as long as you materialize them inside. It's when they escape the block that there is a problem.
@mark540 the way it's usually stated is that mixing laziness and IO is the problem
I think really understanding lexical scope vs dynamic scope vs whatever laziness is (deferred realization) is at the root of many, many misunderstandings in Clojure
and separately what happens at read vs compile vs macroexpand vs runtime vs print
I've been working in Java for some time and was surprised to see how often dynamic bindings are used in Clojure, since I consider them the equivalent of thread locals in Java, and although very useful I've always tried to avoid using them as a shortcut to arg passing. But I think I understand that they're traditional in Lisp, and when made very explicit they're OK. Still, I try to avoid them except in certain cases.
I should clarify. Bindings are not equivalent to thread locals, it's just that thread locals can be used to implement bindings. And I think thread locals are better used for caching, for example, rather than to avoid arg passing.
There is great wisdom in this one.
dyn vars remain my guilty pleasure... next time the need arises I'll try to think hard what a proper alternative would be (without introducing a new function argument and passing it around a gazillion defns)
one time we replaced them with a new field in a specific defrecord
(Component pattern). Another possibility would be something like https://github.com/steveklabnik/request_store (s/Rack/Ring)
only real excuse I can think of is, “Ok. I’m writing a lib. I want to make a non-breaking change, and dynavar is the only way forward.”
As someone who inherited a library based on dynvars (`clojure.java.jdbc`) and eventually steered it to a library that is not based on dynvars, I'll echo everything @potetm said: Just. Don't. /cc @vemv 🙂
Sure thing, https://stuartsierra.com/2013/03/29/perils-of-dynamic-scope is well kept into account But lib != app, particularly a large one that might have inherited some complexity In an app, passing args around can mean "couple everything to everything else", so things like Component or request storage can shine
Technically, often the code is coupled either way. It's just a question of whether the dependency is clear or hidden. Dynavars don't remove the fact that one piece of code is dependent on the other.
having a chain of N namespaces :require
ing each other is a form of coupling, that is forced when going the defn route
Component or request storage
(those were my words, not "dynavars") allow ns's to not depend so strongly on each other, allowing certain modular designs
usual caveats: mistakes, time, priorities, etc etc
I think the pattern that is attempted to be avoided is when you have layers of code (let's use system -> view -> controller -> model for arguments sake). If you add a new thing to your system that a model function needs, then the view, controller and model function need an additional argument of "foo" to be able to pass it down to the model.
To be clear, I'm not certain it's a bad thing that they all need updating. I'm just trying to articulate the problem in order to push discourse forward.
One solution to this is stamp coupling, but I find the vagueness of that very confusing.
I would argue, for the case of a standard webapp, you basically need: 1) a handler fn for a route, 2) some helper fns. Everything else (e.g. “domain”, “service”, “controller) is an arbitrary, and unhelpful, cut.
How do you divide your handler functions up? Where do they live? By what they operate on? What if they operate on multiple Entities?
(these are genuine questions which I'm in constant pursuit of novel answers too, as I think they help define a strategy very well.)
The only impact it has is, “How fast can I find the fn I’m looking for?” Keep shuffling them until the answer to that is, “It doesn’t bother me.”
As long as handlers aren’t co-dependent (don’t do that), they can be moved ad nauseam with literally zero impact (negligible cognitive overhead, zero runtime impact).
Do you have one big namespace with everything in? How do you decide when to put something in a different namespace?
I know that’s not very satisfying. But it’s important to remember: this will not make or break you. It’s arbitrary, and it’s super easy to change.
The entire point of shuffling is to find things faster. So try to keep an eye on that and update when it “feels slower.”
Interesting. This aligns quite well with something I have been thinking about, that I think is relevant to this decision. Your unit of modularity is the function, not the namespace. (ish).
> What are your “tip of the day” topics you would’ve liked to see when you were starting out? Interesting stuff ofc, but more various tips are welcome!
To be clear, I'm not certain it's a bad thing that they all need updating. I'm just trying to articulate the problem in order to push discourse forward.
Hey guys, I'm fairly new to Clojure. I come from a full stack Javascript background. I'm planning on building a production REST API in Clojure and am interested in knowing what's the standard libraries/frameworks that's mostly used by Clojurians for this purpose. In my initial research I see a lot of options like Ring/Compojure, HTTPKit, Pedestal etc. Which one of these are the most prevalent and maintained? In the Node.js world, Express is what most people use to build web backends. Is there an equivalent in Clojure?
Equivalent is probably ring/compojure. Some people outgrow those and move to more data driven things like pedestal or compojure.api or yada. Others keep using the basics of ring but maybe swap routers to something like reitit
Httpkit is just an implementation of a ring server. The most popular is embedded jetty but some people opt for undertow, httpkit, tomcat, etc. I'd start with jetty if you're not aware of a reason to choose something else. The jetty implementation is bundled with ring by default
Thanks for the helpful summary @rutledgepaulv. What is the state of things on the Database/ORM side? What libraries should I be looking at? My REST API would mainly be doing CRUD operations on Postgres for the moment.
If it really is just CRUD, have you considered https://postgrest.org/en/v6.0/. Apologies for the clojure person years I've just destroyed but I'd still hire them and get them doing more interesting stuff.
@U20DT8LSE My recommendation is to use https://www.hugsql.org/ for clojure + sql. Use sql for what it's good at, then use clojure for dynamically generating functions to call your sql expressions. Your DBA's will love you, and you will be doing basically vanilla SQL + Comments, and you can leverage the full power of the SQL database of your choice (PG in this instance) without fighting an ORM (One of the many classic ORM problems)
This is actually very topical at the moment as I think it's basically what @U050CTFRT was talking about yesterday at re:clojure. We shouldn't be making "artisanal, hand-crafted CRUD APIs", for each company who needs one. We should be writing (or using) an API that can be put in front of any database (or rather any instance of a database of a specific type).
I have used it in couple of my projects and it turned out well..
Simplfied EDN Query Language for SQL. https://github.com/exoscale/seql Turn Clojure data structures into SQL. https://github.com/jkk/honeysql
@U20DT8LSE Clojure alternate to ORM: https://walkable.gitlab.io Demo: https://github.com/walkable-server/realworld
I built a production-able RESTful service using reitit. Works great. https://metosin.github.io/reitit/
Thanks for the helpful summary @rutledgepaulv. What is the state of things on the Database/ORM side? What libraries should I be looking at? My REST API would mainly be doing CRUD operations on Postgres for the moment.
Hey quick question. When returning an empty list with ring, why does the return type become octet stream, event though I have wrap-json-response middleware?
*content-type
(def app
(-> (wrap-defaults app-routes api-defaults)
(wrap-cors :access-control-allow-headers [:accept :origin :user-agent :x-request-with :cache-control :cookies]
:access-control-allow-credentials "true"
:access-control-allow-origin [#"^(http(s)?://)?localhost:(\d){4}"]
:access-control-allow-methods [:get :put :post :options :delete])
wrap-cookies
;; print-request
(wrap-authentication backend)
(wrap-authorization backend)
logger/wrap-with-logger
(wrap-json-body ,,, {:keywords? true :bigdecimals? true})
wrap-json-response))
Probably because something in there calls seq
on the response (which returns nil
from ()
)?
You could try explicitly returning []
(empty vector) to see if it special-cases vector?
before it calls seq
(just guessing).
I explicitly returned []
from the function , and in both cases the route/function returns [ ]
with application/octet-stream
(defn get-users-events
[request]
(if (authenticated? request)
{:status 200
:body (json/encode (find-users-events
(:user
(:identitiy request))))}
(throw-unauthorized {:message "unauthorized"})))
If you return a non-empty vector, does it work as expected? (to confirm whether the empty sequence is a special case)
it does not, it returns the vector (not empty) but still has content-type octet stream
this is mysterious, because I have another resource that returns json, and they are the same
hmmm, it looked into it, and the other resource also returns octet stream
After looking closely at the other resource I finally got it to work, the issue was that the frontend go-block was misbehaving, and that was where I was testing the interface. Thanks for the help @U04V70XH6
OK, so it's not related to being empty. Ah, OK, I don't think wrap-json-response
actually sets the content-type maybe? I seem to recall running into this with some of the middleware and being surprised the content-type is not set automatically...
I didn’t get it to set content-type
though
hmmm, so how would you recommend setting the content-type?
Looking at wrap-json-response
it only puts application/json
as the Content-Type
if it isn't already set...
...so something in the middleware stack is setting the content type for you (probably in the default middleware? And by the time wrap-json-response
gets control, there's already a content type...
I’m a bit new to the language 😅
(def app
(-> (wrap-defaults app-routes api-defaults)
(wrap-cors :access-control-allow-headers [:accept :origin :user-agent :x-request-with :cache-control :cookies]
:access-control-allow-credentials "true"
:access-control-allow-origin [#"^(http(s)?://)?localhost:(\d){4}"]
:access-control-allow-methods [:get :put :post :options :delete])
wrap-cookies
;; print-request
(wrap-authentication backend)
(wrap-authorization backend)
logger/wrap-with-logger
(wrap-json-body ,,, {:keywords? true :bigdecimals? true}) wrap-json-response))
This is my middleware, do you see anywhere that might cause the issue? 🙂maybe setting wrap-json-response before wrap-cors?
wrap-cors
should almost certainly be on the outside of the stack but it's wrap-defaults
that is setting the content type for you.
that didn’t work…
Middleware ordering is a thorny problem.
haha yeah I’ve had my fair share of frustrations these past few weeks, I must admit 😅
If you are only returning JSON, you can put wrap-json-response
closer to your handler than wrap-defaults
good idea 🙂
I would put the json body / response at the top, then defaults, then auth, then logging, then cors.
hmmm still doesn’t set the content-type
Also note that defaults includes wrap-cookies
already.
Paste your current middleware stack/wrapping...
(def app
(-> (wrap-json-response app-routes)
(wrap-json-body ,,, {:keywords? true :bigdecimals? true})
(wrap-defaults ,,, api-defaults)
;; print-request
(wrap-authentication backend)
(wrap-authorization backend)
logger/wrap-with-logger
(wrap-cors :access-control-allow-headers [:accept :origin :user-agent :x-request-with :cache-control :cookies]
:access-control-allow-credentials "true"
:access-control-allow-origin [#"^(http(s)?://)?localhost:(\d){4}"]
:access-control-allow-methods [:get :put :post :options :delete])))
I would add some logging middleware between wrap-json-body
and wrap-defaults
so you can see what headers are set at that point...
That order looks pretty similar to our order at work for an API that always returns JSON...
haha, then it must be something I’m doing wrong outside of the middleware
I’ll publish the calls all the way down to the db call
This:
(GET "/event" request (get-users-events request))
calls:
(defn get-users-events
[request]
(if (authenticated? request)
{:status 200
:body (json/encode (find-users-events
(:user
(:identitiy request))))}
(throw-unauthorized {:message "unauthorized"})))
Which in turn calls:
(defn find-users-events
[user-id]
(let [response (db/lookup-a-users-events user-id)]
response))
And then lastly calls:
(defn lookup-a-users-events
[id]
(jdbc/query psql-db
["SELECT * FROM event WHERE owner = ?" id]))
Ah, you are not letting wrap-json-response
handle the JSON conversion. You're doing it yourself, explicitly.
You should not need that json/encode
call -- that's the whole point of wrap-json-response
thank you!
Has that fixed it?
it has!
prefectly
perfectly
(the irony of writing perfectly imperfectly)
https://github.com/ring-clojure/ring-json/blob/master/src/ring/middleware/json.clj#L115 -- wrap-json-response
only takes action if the response is a collection (not a string, which is what json/encode
returns).
I really like how helpful the clojure community is. Thank you @U04V70XH6
:message Could not locate clojure/spec__init.class, clojure/spec.clj or clojure/spec.cljc on classpath.
when was clojure.spec a thing? was there a clojure.spec -> clojure.spec.alpha migration?
there's no such ns as clojure.spec, the ns is clojure.spec.alpha
spec was added as a dep of Clojure in 1.9.0
if you could share a full stack trace, might be able to help pinpoint the issue
but agree with noisesmith that something is trying to load clojure.spec namespace, which is not a thing
maybe some lib was using a pre-release of clojure.spec that did have a clojure.spec ns?
well I have some requires lying around on clojure.spec
still so most likely just that
that ns did exist in 1.9 alphas of Clojure but I'm not aware of anything out there using those
and clojure.spec.gen.alpha
I just installed clojure on NixOS using nix-shell -p clojure
. I ran clj
, it downloaed some packages, and started the repl successfully. It created a ~/'?'/.m2
folder to hold the packages. My question is, is this ~/'?'
folder normal?
if it helps, the standard location for the cache is ~/.m2/repository
- maybe it's doing something fancy with HOME?
Maybe someone fat fingered it? I always accidentally type ~?.m2
may be https://discourse.nixos.org/ could be a helpful place to ask?
is it considered bad practice to try and deliver a lazy sequence through ring’s piped input stream? is there any guarantee this will realize it?
why doesn't this produce #'foo/x?
$ clj -e "(ns foo) (ns bar) (binding [*ns* (find-ns 'foo)] (def x 1))"
#'bar/x
(defmacro defx [name value]
(declare name)
`(intern (find-ns '~(ns-name *ns*)) '~name ~value))
this is why (defn f [] (def foo 1))
creates foo in the current ns, with value unbound