This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-04-22
Channels
- # announcements (2)
- # architecture (33)
- # babashka (4)
- # beginners (445)
- # bristol-clojurians (10)
- # calva (23)
- # cider (43)
- # clj-kondo (36)
- # cljs-dev (13)
- # cljsrn (20)
- # clojure (136)
- # clojure-argentina (8)
- # clojure-dev (1)
- # clojure-europe (18)
- # clojure-germany (1)
- # clojure-italy (5)
- # clojure-nl (45)
- # clojure-spec (66)
- # clojure-uk (29)
- # clojurescript (69)
- # conjure (157)
- # cursive (2)
- # datomic (216)
- # emacs (10)
- # exercism (4)
- # figwheel-main (8)
- # fulcro (30)
- # graphql (21)
- # hoplon (5)
- # kaocha (7)
- # leiningen (3)
- # luminus (4)
- # music (1)
- # off-topic (24)
- # pathom (10)
- # re-frame (19)
- # reagent (11)
- # reitit (16)
- # remote-jobs (1)
- # ring-swagger (5)
- # rum (7)
- # shadow-cljs (125)
- # spacemacs (8)
- # sql (9)
- # tools-deps (12)
hey team another beginner question.
i have two tables right now user
and address
. the address
table relates to the user
table. in java/spring world when i query the database i get back a user object that has a nested address object in it
{
address: { ... }
}
when i do this in clojure the map returned is merged together so i get 1 object back with all the data at the top level. how would i do this in clojure? i am using next.jdbc@tjb result sets are flat -- by default, next.jdbc
returns qualified keys in results so you'd have :user/id
, :user/col
, :address/id
, :address/street
etc. That's idiomatic in Clojure, to have a flat hash map with qualified keys.
Not sure what "map" you mean. next.jdbc
already gives you a hash map.
so i get something like this (all dummy data so IDs are ok to share here)
{"address/id":"2b6d3a10-acf5-4142-a7cd-2b0f83a85b37","address/contact_number":"","app_user/first":"Yusuke","address/user_id":"c0c03eff-9870-4c0e-83ac-506bf59d69cf","address/country":"USA","app_user/email":"","app_user/id":"c0c03eff-9870-4c0e-83ac-506bf59d69cf","address/state_region":"IL","app_user/last":"Urameshi","address/postal_code":"60622","address/extended_street_address":"Suite 420","address/street_address":"123 Money St.","address/city":"Chicago"}
however i was going to hope i could get this to return like
{
first: ""
last: ""
address: {
id: ""
}
}
Keys are strings? Oh, you're converting to JSON?
So you want to convert the idiomatic internal Clojure format into a specific nested format to return to your clients?
and im mostly jsut trying to see if i need to roll this on my own, which im ok with just want to make sure
It's just Clojure data. Transform it however you need to get the format that you want the API to return.
to manipulate the database response into whatever format and then return that to the client?
I'd say so -- your database format is going to be optimized for storage/queries; your API input/output is going to be optimized for clients. Those won't necessarily be the same (but might be in some cases).
from my experience in the java world your domain models mimic how your database is set up
thank you again for the insight @seancorfield!
Yeah, that's why ORMs are so terrible.
I've used a wide range of ORMs over a couple of decades and I've hated every single one 🙂
I agree with Ted http://blogs.tedneward.com/post/the-vietnam-of-computer-science/ 🙂
A follow-up article by Jeff Atwood https://blog.codinghorror.com/object-relational-mapping-is-the-vietnam-of-computer-science/
but i think i just need to write a custom mapper to map the db response into the shape i want
No. Resist. You don't need records. Unless you want type-based dispatch.
If you want to collect out attributes based on the qualifier, you can call namespace
on the keyword column name to get just the qualifier as a string... that might help automate it...
That will give you a map where the top level keys are the qualifiers and each one has a map of the unqualified keys that belong to it.
Would be each row, yes.
So then you could do (assoc (:user data) :address (:address data))
to get your nested map.
If you had a bunch of things joined and thus multiple nested things, you could do (merge (dissoc data :user) (:user data))
bah! this is a tangential question: why are my keys from my db strings and not :address
. when i call my database i get "address":
next.jdbc
returns them as keywords.
{address {user_id #uuid "c0c03eff-9870-4c0e-83ac-506bf59d69cf", country USA, contact_number +, extended_street_address Suite 420, city Chicago, postal_code 60622, id #uuid "2b6d3a10-acf5-4142-a7cd-2b0f83a85b37", street_address 123 Money St., state_region IL}, app_user {first Yusuke, email , id #uuid "c0c03eff-9870-4c0e-83ac-506bf59d69cf", last Urameshi}}
Looks like you handed the JSON to the reduce-kv
instead of the database result?
(defn get-user
"Get user by id"
[id req]
(let [db (-> req :database)
uuid (string-utils/to-uuid id)
data (reduce-kv (fn [m k v] (assoc-in m [(namespace k) (name k)] v)) {} (first (repo/get-user db uuid)))]
(println data)
(assoc (:app_user data) :address (:address data))))
(defn get-user
"Get user by id"
[db id]
(sql/query db
["SELECT *
FROM app_user au
INNER JOIN address add ON au.id = add.user_id
WHERE au.id = ?" id]))
Oh... sorry, my code was a bit too off-the-cuff. Yes, namespace
and name
produce strings. So the DB result was with keywords but my reduce-kv
turned them into strings.
> So then you could do `(assoc (data "user") "address" (data "address"))` to get your nested map. ^ that will give you almost your JSON
Or (reduce-kv (fn [m k v] (assoc-in m [(keyword (namespace k)) (keyword (name k))] v)) {} results)
That will produce the keyword-based versions instead of strings.
(that's what I get from coding in my head instead of in the REPL 🙂 )
{"address":{"postal_code":"60622","extended_street_address":"Suite 420","city":"Chicago","contact_number":"+","state_region":"IL","id":"2b6d3a10-acf5-4142-a7cd-2b0f83a85b37","street_address":"123 Money St.","user_id":"c0c03eff-9870-4c0e-83ac-506bf59d69cf","country":"USA"},"app_user":{"first":"Yusuke","email":"","id":"c0c03eff-9870-4c0e-83ac-506bf59d69cf","last":"Urameshi"}}
The nice things about Clojure's "data first" approach is that you can slice and dice this any way you want.
(defn get-user
"Get user by id"
[id req]
(let [db (-> req :database)
uuid (string-utils/to-uuid id)
data (reduce-kv (fn [m k v] (assoc-in m [(keyword (namespace k)) (keyword (name k))] v)) {} (first (repo/get-user db uuid)))]
(println data)
(set/rename (assoc (get data :app_user) :address (get data :address)) {:app_user :user})))
(defn get-user
"Get user by id"
[id req]
(let [db (-> req :database)
uuid (string-utils/to-uuid id)
data (reduce-kv (fn [m k v] (assoc-in m [(keyword (namespace k)) (keyword (name k))] v)) {} (first (repo/get-user db uuid)))]
(set/rename-keys (assoc (get data :app_user) :address (get data :address)) {:app_user :user})))
gives me
{"first":"Yusuke","email":"","id":"c0c03eff-9870-4c0e-83ac-506bf59d69cf","last":"Urameshi","address":{"postal_code":"60622","extended_street_address":"Suite 420","city":"Chicago","contact_number":"+","state_region":"IL","id":"2b6d3a10-acf5-4142-a7cd-2b0f83a85b37","street_address":"123 Money St.","user_id":"c0c03eff-9870-4c0e-83ac-506bf59d69cf","country":"USA"}}
Why the rename
? You don't have :app_user
in that hash map.
Also, you can use (:app_user data)
and (:address data)
-- that's more idiomatic when you have known keywords @tjb
"app_user":{"first":"Yusuke","email":"","id":"c0c03eff-9870-4c0e-83ac-506bf59d69cf","last":"Urameshi"}
No, (:app_user data)
won't contain that key.
And then you assoc
in :address
Right.
(set/rename-keys (assoc (:app_user data) :address (:address data)) {:app_user :user})
to rename :app_user
to :user
data
is {:app_user stuff1 :address stuff2}
so (assoc (:app_user data) :address (:address data))
will give you stuff1
with a new key :address
whose value is stuff2
But it's not in that data.
Look at your result -- it doesn't contain user
.
There's no :app_user
in that structure.
:app_user
is in data
, but that's not what you're assoc
-ing into.
And that's what you'll have -- without the rename
And you Dan 🙂
There's a great library called camel-snake-kebab for converting names.
At work, our JS client team use snake_case for interop with our Clojure backend but I think they also transform into (different) data structures with camelCase
(defn jsonify-routes
[routes]
(-> routes
(wrap-json-response)
(wrap-json-body)
(wrap-json-to-camel-case)
(wrap-params)
(wrap-multipart-params)
(wrap-cors :access-control-allow-origin [#""]
:access-control-allow-methods [:get :put :post :delete])))
yeah. there's some pain of json versus postgres versus edn types. postgres doesn't like hyphens or question marks. edn needs to become json in the db but can become edn again coming out. we tuck these thing inside a db layer and don't see them and then just declare some stuff
Middleware could automatically transform all inbound and outbound keys for you...
Our DB layer is (mostly) snake_case these days (we started off with camelCase but quickly decided that was a bad idea! 🙂 but of course we still have legacy tables with camelCase names).
do yall tack on question marks to question-y columns? :state "cancelled"
type things, do those become keywords?
I try to avoid ?
on keywords.
I'm on the fence about ?
on local symbols. Some days I feel more favorable to it than others 🙂
Re: DB layer and domain layer naming -- we've gone back and forth on that over the years. For a while I was in favor of using :identifiers
(c.j.j) and builder adapters (next.jdbc) to perform automatic conversion coming out of the DB (and :entities
/ :column-fn
for going into the DB).
But now I'm more inclined to let :snake_case
leak out of the DB layer and into the surrounding code...
It's fewer moving parts at the expense of some slightly less idiomatic keyword names.
(defn wrap-json-to-camel-case
[handler]
(fn
([request]
(let [response (handler request)]
(assoc response :body (cske/transform-keys csk/->camelCaseString (:body response)))))))
(defn jsonify-routes
[routes]
(-> routes
(wrap-json-to-camel-case)
(wrap-json-response)
(wrap-json-body)
(wrap-params)
(wrap-multipart-params)
(wrap-cors :access-control-allow-origin [#""]
:access-control-allow-methods [:get :put :post :delete])))
it seems to me, (def a (atom some-non-persistent-java-container)) is totally meaningless.
swap!
on an atom uses a JVM method that effectively uses identical?
to check whether the item is equal or not equal, so a JVM mutable collection that remained identical?
across mutation operations inside of a swap!
operation would be buggy.
The intended use case is to hold immutable values inside of atoms, and most Clojure mutable reference types.
@tjb Glad you got it working! You could do (update response :body #(cske/transform-keys csk/->camelCaseString %))
instead of that assoc
if you find that reads easier.
And of course now you have middleware, you could auto-convert your input parameters (:params request)
to kebab-case as well.
Hello clojurians, how to store integrant system for later access in repl? I want to call my reitit routes from repl after initialing integrate system.
;initializing handler key like this
(defmethod ig/init-key :shop/handler [_ {:keys [db] }]
(router/create-app db))
; and initializing system
(defn -main []
(println "Running main!")
(ig/init system-config))
I think https://github.com/weavejester/integrant-repl is what you're looking for.
Thanks @UJF10JP8A I tried to add integrant repl and start/stop is working. but did not knew that we can get system of repl/state. thank you!
so, I am really in trouble with this pretty print because at the moment I do not have a week to spend just reading up on all the possible variants, I would just like to see an example tool or even example usage of the built-in pprint that prints formatted values, or at least indented and newlined values 🙂
I don't know where to look for the bits to tweak, and how to turn and nib at them to do what I wish it for to do
@ashnur are you developing in the browser?
I know I could do clj->js and use JSON.stringify but I would like a less indirect solution 😄
you could also try (js/console.log (clj->js val))
and then take a look in the browser console
https://github.com/brandonbloom/fipp I tried to use this but it made things worse, not better, it added a bunch of newlines but nothing else
https://github.com/yogthos/json-html this works more or less, although it was a bit of a struggle still 🙂
> and use JSON.stringify @ashnur i mean use the console without doing this
i ask because i did get some use out of: https://github.com/binaryage/cljs-devtools -- it just requires looking at the browser console
there's also a hard-core version by the same author (https://github.com/binaryage/dirac)
@chris358 you are ignoring all what I wrote apart from some bits and pieces 🙂 but thanks nevertheless for attempting to help and I would like to reiterate that the last link has solved my question, it works and I am now trying to do something else
good to hear you found something that works -- it wasn't clear to me from your "more or less"
thanks @sogaiu will try those too, but yeah at the moment I just wanted inline in the browser window
Hey there, i've been looking around for a clojure book to buy, so that i can start learning clojure and clojure script, what would you guys recommend?
Everything i found seems kinda outdated, books from 5 to 10 years ago. Would they still be relevant today?
For “getting started stuff,” Clojure is pretty stable over the 5-10 year time frame, so you can probably get a good start even with the older book.
Clojure for the Brave and True is frequently recommended for beginning Clojure https://www.braveclojure.com/
And there’s also a #braveandtrue channel if you have questions
Is that also true for the clojure script enviroment?
I've been through most of brave and throught and Programming Clojure
I was looking more for a book that could give me an overview of how to really build something
A lot of Clojure stuff transfers over cleanly to cljs, but there’s a few differences due to the underlying language being js rather than java
when i say most, that might be an overstatement, (i finished the basic stuff)
Once you have the basics down and are comfortable solving small problems/exercises, it starts to get to be more a matter of putting together libraries and building applications out of that
Exactly, but what would be good resources for finding the right tools? How to i build the dev stack?
All of this seems very different from what i'm used to...:(
There's no framework that gets everything you need right?
Something like django or laravel
Yeah clojure is more library oriented than framework oriented. There’s luminus, which is kinda framework-y (https://luminusweb.com/). Luminus basically puts together a bunch of libraries for you, but that can be overwhelming for beginners because there’s so much to learn at once
A lot of people recommend starting with just a simple ring/compojure server, and adding each library as you need it, so you can absorb each library one at a time.
You might also want to look at Sean Corfeld’s sample library at https://github.com/seancorfield/usermanager-example
tks a lot, i'll be looking into that
but just to clarify, if i get a cljs book from 2012, the tools that are used in the book would still be relevant today?
i was looking at web dev with clojure
I’d say still useful from a learning perspective, maybe do a test/sample project with it and then once you’re familiar with the basic libraries, you can start looking at what other libraries might be good. For example, Compojure is probably the most widely used library for basic routing, but there’s a new library called reitit that I really like (though it’s still maturing). So compojure is good to learn and you’ll get lots of help with it, and once you know it you might want to check out reitit or one of the other routing alternatives.
OK, i'll try to build something basic with compojure and ring, and see how that goes...^^
but what would be the big difference between compojure and reitit?
Is it that one would be better in a specific situation?
reitit describes routes as data. Compojure describes routes via code (macros). I think reitit supports bidirectional routing but Compojure does not.
@jose.ferreira.ptm2 You asked about a "cljs book from 2012" -- ClojureScript, specifically? I believe the recommended tooling for ClojureScript has changed dramatically since then and I think macro-handling in cljs has changed too... but I don't actively use ClojureScript (just Clojure) so I can't be sure of that (there's a #clojurescript channel where you can probably get more complex/accurate cljs questions answered)
tks a lot sean, thats what i feared...
im trying something simple for myself now, using just clojure with ring and compojure as was recommended by @manutter51
I'll decide wich book to buy when i get stuck with something and feel that i need some more structure to learning this stuff
I think i was looking at this all wrong. I'm used to big frameworks that do a lot of the big structuring work for you
and are very oppinated...
i guess thats just not the clojure way
There is a recent 3rd edition of web dev for Clojure, much more recent than 2012.
That 3rd Ed is in progress but is definitely getting overhauled content in the cljs stuff (I bought it in "prerelease" form and get updates as new sections as written).
i missed that
it seems its not out yet thou
You can buy the preview/beta ebook and get updates as they come out.
(that's what I did)
i'm old school in that regard, i like to have my books in physical form
Do you recommended it?
Last update was March 25th. Beta 7 of the ebook.
if you've read it already...
It's a good book (3rd ed -- 2nd ed is outdated) but I'm not a fan of Luminus really. It makes a number of choices I don't like.
But, for learning web dev with Clojure, it's probably the best option. Be aware that the first chapter or two are really overwhelming because they throw all of Luminus at you, then later chapters go back to basics and walk you through most everything from the ground up. I think that's a bad idea -- I'd prefer to see Luminus only introduced once the basics are already covered -- but, hey, I'm not the author and he's the one who wrote/maintains Luminus too so it's not surprising it's featured upfront in his book 🙂
shameless plug....xD
but yes, i guess i understand the choice...show you the cappabilities, and then explain it in more detail
top down aproach
My recommendation, much like @manutter51’s, is to start with the basics: Ring, Compojure, then maybe Selmer for web pages, and Component for managing the lifecycle of things (like web servers and database setup), and get comfortable with that on the server first. Then start in on ClojureScript using the official docs and recommended tooling and, again, get through the basics first. Then look at combining them into a full-stack app.
ok, selmer seems pretty straightforward, just a template system....
component seems like a more alien concept...
What kind of thing are you trying to build? (=
nothing in particular right now...just dabbling
i saw a few talks about this and got curious
just managed to say hello world to the browser,
oh exciting
lets hope it gets a little bit more exciting than this
Yeah, there are a few different projects for the web-based project, but you must be comfortable with compojure and ring. getting good with those for GET/POST actions will make things easier later on. like if you're doing authentication for users logged in or not you would do it on the layer of ring/compojure then when you're serving html, you can use different templating libraries like selmer ... then when you want to go more elaborate on the clientside you can use figwheel to start a mini clojurescript prototype, and when you are comfortable with clojurescript you can use it to make react components using Rum and if you need to make a real-time webapp with responsiveness between server and client you can use Sente as a starter project which has both server and client starting points for you.
Fulcro is also a big solution for the big problem of web applications with a serverside and responsive clientside based in js, i would recommend looking at it if you need a robust solution for something gigantic one day. There are many useful concepts in all of the suites but it's far easier to do software maintenance longterm when you pick and choose only the components you need.
i like how everything is very modular around here...
I would hang my stomach up on the wall while not using it if I could xD
i just find that that are a lot o tools that do basically the same thing, (or i'm just not understanding the differences between them)
Hey @jose.ferreira.ptm2 I’m working on CS50 Project1 ( https://docs.cs50.net/web/2020/x/projects/1/project1.html ) but in Clojure right now, and learning clojure on the process. If you want to take a look this is the link https://github.com/orlando-mendoza/cs50_project1_clj
Yeah it's hard to call it redundancy per se because a lot of projects do things in a slightly different style but achieve the same result. Basically, many projects are black boxes (you can always examine the source code so .. transparent boxes?) that are made to achieve some sort of output for some given input and the implementation is ... 😄 whatever it needs to be. Oftentimes if one is not serving you, you can drop in another library with little or no editing
I want to map
through a collection and in the mapping function I would like to have access to the rest of the collection, ie: the whole collection minus the current element. I didn't find anything that does exactly that so I created a new function map-with-group
(Not fond of the name) that does this. I was wondering if this is the right approach. Or if it is too much work that can be easily expressed within a regular map or other constructs. Here is the function:
(defn map-with-group
"maps through the given collection while holding a list of
all the elements in the collection excluding the current element in the mapping.
f: Is a function that recieves two arguments
1. The current element.
2. The collection minus the current element.
coll: The collection."
[f coll]
(let [group (apply assoc {} (interleave (range (count coll)) coll))]
(map #(f (second %) (vals (dissoc group (first %)))) group)))
@orlandomr27 {:status 200 :body "Olha a cara do bis!" :headers {}})
pt or br?
the register-and-login branch has more features. I’m working on register form
yeah, i'm looking through
So this is basically a book DB right?
what are you using as your learning resources?
a lot of things but mainly Adam Bard’s blog and video about web app in Clojure. http://PurelyFunctional.tv and LambdaIsland videos
take a look at project1 requeriments and you are invited to PR on my project if you want. We can learn together in the process of building this app
tks a lot
do you have a link for the project?
I’m confortable with spanish and portuguese yes :)
@sova yes, but the entry point for someone who starts looking into this seems very convuluted
I guess this is part of the process for clojure maturing curve. Considering that clojure is a relativaly new lang. Some libs will just endure and some not until the whole ecosystem reach a point that for example python has grown.
yes, but clojure is not really a new language...
neither is LISP, and this has been a problem for all LISPS
I agree.
Clojure is designed by/for experienced developers so on-boarding beginners has never really been a focus. The beginner experience has gotten a lot better over the years but it will always have a steep on ramp for beginners because of its focus and philosophy: composition over libraries over frameworks, abstractions over concretion, etc. The analogy is often made to a violin -- in expert hands it is beautiful but a violin is extremely difficult for a beginner to start making music with.

In almost every problem space, Clojure has multiple solutions that each make different trade offs (e.g., Mount vs Component vs Integrant vs ... -- just for managing state and start/stop lifecycles)
ok, but if you want mass adoption of a programming language, that doesn't seem like a very good strategy
wouldn't it be beneficial?
See Stuart Halloway's tweets like this https://twitter.com/stuarthalloway/status/1249772079068700674 (he and Rich are the primary stewards of Clojure)
I'm curious as to what you think would be beneficial about mass adoption?
well, a bigger community would mean more people contributing
isn't that what makes languages flourish?
You were just complaining about how many libraries you already have to choose from 🙂 And contributions to Clojure are very strictly vetted so more people isn't going to make Clojure itself evolve any faster.
ahahah
yeah you're right about that one
seems like a small but very productive community
but mass adoption would also bring more attention, and there would be more jobs
Many Clojure libraries are very small and focused on solving specific problems -- which means they are often "complete" fairly quickly and then don't need much maintenance going forward.
Lisps have been failing to take over the world continusly for decades now
Most Clojure shops are very small -- because a few people can get so much done so quickly with the language. There are some places that have huge teams -- NuBank in S. America has hundreds of Clojure developers -- but those are rare indeed. Lisps are just seen as too "weird" by most mainstream developers 😐
but the concepts are awsome...
i belive if the entry point wasn't so hard, there would be a lot more people ready to take it
and by that, i don't mean lang syntax or semantics
i just mean organized content
like roadmaps
JS is very convuluted also, you have hundreds of libs and frameworks as well
but you can find a lot of content, and a lot of guides
And a lot of that JS content is quickly outdated because of all the (backward-incompatible) churn in libraries and frameworks etc 🙂
I've been building software for about 35 years and I find the JS ecosystem to be absolutely bewildering and extremely hard to navigate. Libraries and frameworks continually change and break things. There is "too much" documentation so finding good, up-to-date material is harder and books are out of date just months after their release. Even the language itself is changing constantly so "best practices" are outdated within months sometimes and the tooling is really brittle and really complex. At work, our front end is built is JS, with React/Redux/etc, and watching our front end team wrestle with build and test tools, and performance, and all sorts of weirdness in browser and device support... I have huge respect for their ability to get anything done!
I find Clojure's ecosystem to be a model of simplicity and stability by contrast.
Occasionally I have to write a bit of JS and every time it is an exercise in frustration for me!
The on-ramp with JS is very gentle tho'. You can hack stuff together quickly because it's just like all the other C-family languages out there. Sure the prototype OO stuff is a bit weird but it's all based on mutable, imperative practices that are fundamental to almost every other language. The on-ramp with Clojure is very steep because it focuses on immutability and abstractions -- you can't avoid any of that stuff, and pretty much everything you know from "regular" languages has to go out the window (and the more steeped in OOP/mutability you are, the harder Clojure can be to learn).
So, yes, Clojure is hard to learn. I think it's easier if you've never programmed at all than if you have "only" experienced either JS or Java.
(which is all my way of saying "don't get discouraged -- this is hard and we all know and accept that" 🙂 )
i totally agree with you, but you're missing my point (or i wasn't able to get it across correctly)
it's not the syntax or the philosophy of the language that i'm "complaining" about
i get that it's different, i had a few prior experiences with lisps before
its just that there are very few beginner content...
You say that, but having been hanging around helping beginners for a while, I think the issue IS having to learn unfamiliar concepts
you have a lot more experience than me on that regard then, but if the begginer content was more widly available and digestible, all that unfamiliarity would be a lot easiear to handle
Well, what I see is people get the impression that their struggle is due to the lack of "easy to digest" tutorial and guides
But it's not that their are no guides or tutorials for everything, it's that none of them are "easy to digest" because the thing their trying to explain can't be reduced to a few sentences and a comparison with JS
So really, it's that the material isn't "easy to digest" and so it can't be explained in a "easy to digest" manner either
And I'm not just talking about the concepts of say how higher order functions work. I'm talking also about the pragmatic concepts like how the JVM works, how the Clojure evaluation model works, how the interop works, how the language is bootsrapped, how dependencies work, how compilation works, how the REPL works, etc.
Though I'll grant you that there's not a lot of material and documentation around common frameworks. Like how ring works, how compojure works, how reagent works, etc. A bigger community would probably help here.
That said, and this is me ranting a bit now, I feel often people coming from web dev backgrounds arn't really trying to learn the language, but are only trying to learn the frameworks. And so they start complaining there's no material to teach you the framework, there's not even a clear choice of framework, and that it seems that most framework lack all the features they'd need, etc.
And that's all true, those things arn't there, and if they were they could happily avoid learning the language, and just navigate it at the surface, leveraging frameworks and framework based guides and tutorials
And the answer they normally get is, once you learn the language proper, you won't care for a framework or those guides, you'll just know how to put together your own little framework and be done with it.
Which is... A lot to ask, like this is part of the "steep learning curve". So you're back at having to learn a lot of unfamiliar things.
But I don't have any solutions to this, because that's truly what people who use Clojure[Script] actually do 😝. They each setup their own preferred stack with lots of custom in-house bits and pieces. They don't use this common framework which has a dedicated team behind it where all simply make use of it and wait for it to have new features.
yeah, i get what you-re saying
i was also comming to clojure with the wrong mindset, looking for a structered framework
where everything was setup for me
i'll tackle on, eventualy all those little things will become second nature
the truth is, this is harder, but its also better. it forces you to learn about all the little things
that are done automaggicallyby framworks like django or laravel
And I don't know if we should have a nice full featured framework or not. I think people who know Clojure well manage without it, and don't feel enough pain from that to be motivated in contributing to build a big common framework. So like, maybe it be nice if we did, even for me, maybe I don't think I need one but would still benefit a little from one. And for sure it would make Clojure more approachable to some people. But reality is there isn't, and no one seems motivated to build it. And when some tried, nobody got on-board, the community didn't all homogenize behind any one framework.
yes, it might not even be needed... but that is the norm form people comming from other paradigms
so thats the first thing they'll be looking for
So I just mean, as someone learning, you need to know that things are different here, and that means lots to learn and re-learn. But ya, I think it is worth the investment obviously. But if you're looking for things to be like elsewhere, you'll just be frustrated. Go to accept it isn't. :man-shrugging:
I just think that needs to be explicit
exactly as you were saying before...it his a matter of expectation
Ya me 2 haha. I try to be upfront with people. But I don't know, maybe http://Clojure.org should have a guide saying this, but sometimes people don't read everything on http://Clojure.org either hehe
in languages that have a more wide adoption, you can almost get carried on the lap by all the content you can find to get you sarted
There are very concise guides and explanations
The official Clojure Getting Started guide https://clojure.org/guides/getting_started -- and Alex and the team are often open to additions and enhancements there.
(I'm not trying to be dismissive of the problems for beginners -- having the official getting started / learn Clojure / programming at the REPL material enhanced is going to be the best way to help smooth that on-ramp -- but that feedback pretty much has to come from beginners or near beginners because they know best what changes are needed and what material is missing)
For copyright reasons, contributors to the http://clojure.org site still need to e-sign a Contributor Agreement per https://github.com/clojure/clojure-site/blob/master/CONTRIBUTING.md
yeah, you're right, i guess i was just a bit frustrated for trying to figure this stuff out for 2 days...:)
I'll keep hacking on, and when i get this stuff out maybe i'll try to add my 2cents to help others start
@jose.ferreira.ptm2 as a fellow beginner picking this up right now I can empathize with. There are just a lot of different ways to configure things that achieve the same result. I was finding the combination of lein + shadow-cljs vs figwheel vs lein alone very disorienting (I believe you were working on ClojureScript?) along with just learning the nuances of the language + reagent, etc. I get the sentiment of the community not necessarily being geared for beginners, but I don’t think it takes away from the amazing work in the community to continue building more beginner friendly walkthroughs/guides and making Clojure(Script) more approachable to beginners. I don’t think anyone is actively preventing that, probably the experienced people haven’t felt that pain point in a long term or at all. I definitely plan to try to write some beginner friendly guides in the future when I feel more confident about this ecosystem
I'm not saying that people are actively preventing that, i was just making the point that it needs to be done so that the language can get to a bigger audience
I'm also down to start some guides when i get that AHah moment with clojure... if you want we can cooperate on that end in a few months...:D
@jose.ferreira.ptm2 sorry, definitely didn’t want to insinuate you were saying that people were being antagonistic to beginners. I 100% agree that if the goal was to reach mainstream there should be more work for beginners. Likewise, I’d say that it’s a good goal to be more approachable to beginners/anyone even if the goal wasn’t to reach everyone
Usually what helps me if I can't find a resource on how to do something in Clojure is I'll just google the problem and omit the word Clojure. See how it is done in JS/Java or whatever and then translate that into Clojure
that mostly works, but avoid things like Spring :D
Look on the bright side: at least http://clojure.org has introductory material these days -- when I started to learn Clojure (2010) there was almost no beginner-level material and only a couple of books (Clojure in Action, Clojure Programming, Joy of Clojure were the first three I bought, I think -- 2011, 2012, 2014). Clojure 1.3 was in development. It was a very small ecosystem and community 👀
Important things for me as a beginner… 1. use the repl, write your code and eval, if everthing works, continue adding features 2. read the code of the functions you are using 3. look for examples in github 4. Use the repl again 😁
Hello Clojurians,
I have a quick question regarding Reagent/Hiccup syntax. I was working through a basic Reagent example (https://github.com/mrmcc3/tailwind-cljs-example/tree/master/shadow-cljs) and noticed in the app.cljs file the author used this notation to render a vector of classes without string notation: [:div {:class '[max-w-3x mx-auto pt-12]}
. I messed around with it and found that it this notation (`'[max-w-3x mx-auto]` ) is equivalent to just writing the string: max-w-3x mx-auto
. Is this standard Clojure syntactic sugar or some Hiccup magic? I can’t seem to find any documentation on this use of the single quote as the docs (https://clojuredocs.org/clojure.core/quote or https://clojure.org/guides/weird_characters#_quote) appear to be talking about delayed eval.
Thanks for the help everyone 🙂
@U0122UWNUMC The quote '
means "don't evaluate this". Without the quote [max-w-3x mx-auto pt-12]
would be treated as a vector with three symbols that need to be looked up and their values substituted in. In this case Hiccup wants just literal symbols which it converts into strings so you're right that you could use :class "max-w-3x mx-auto pt-12"
here (providing the string directly to Hiccup) or you could do :class ['max-w-3x 'mx-auto 'pt-12]
-- quoting each symbol to prevent them from getting looked up and evaluated. '[max-w-3x mx-auto pt-12]
is essentially a shorthand for a vector where each element is quoted.
@seancorfield thanks for the detailed response!
Is there any style preference as far you are aware over using the shorthand '[ class-1 class-2]
vs [ "class-1" "class-2" ]
(and I know you wrote some other variations above as well)?
Thanks 🙂
I think in the case of Hiccup, using '[class-1 class-2]
is preferred since it is less typing and you'll quickly get used to quoted literal data as opposed to working with individual elements/strings.
I think another thing which makes Clojure and lisps in general less palatable to the average beginner, is that the people picking them up aren't complete programming novices, but have had prior experience with ALGOL languages. It seems they're suffering the side effects for years, and it tends to make them dismissive of the "esoteric" languages. > Well if LISP is so good and serious why aren't Serious Organisations like NASA using it, hmmm? While in effect they did, until some pointy-haired manager threw it out in favor of C. If Scheme was taught as a first language, maybe things would have been different, but it's very hard to break out of the imperative and OO molds once you've grown into them, it seems. I had new colleagues asking me about design patterns in Clojure, what can you say to that besides "try a function?"
Scheme is taught as a first language in many uni
it was my first language
but is mostly presented as something that you cannot work with, something that is purely academic
So it tends to be dismissed by most students, from my experience
Well thanks a lot for your input guys, i have to leave...
See you later
I don't know if you have tried this, but if learning from a book is a style that can work for you, there are books on Clojure that for not a huge amount of $, can provide a lot of organized material that most people aren't willing to give away for free in blog articles.
Similarly for Eric Normand's on-line videos which cost $. I am not saying that there are no useful free things on-line (there are), but if you restrict yourself to those, you may be missing out on some of the better learning aids.
I think one issue in learning Clojure is in the expectations not being clear. More and more people come thinking, I like to learn by doing a project, maybe put together a little web app. Okay, where's the framework I should use?
@U0K064KQV as a beginner digging into ClojureScript in particular. I have found that there are opinionated guides on Reagent + Re-frame vs Om.next etc. but there just aren’t that many articles compared to looking up “How to build a React project”. There was a comment earlier on how JS has too many guides and with varying quality. I don’t think this is a mark against that community, because as a beginner you can cross reference a lot of guides and it gives you some bearings on what is the accepted best practice/libraries etc. So basically I think there could just be a lot more writings and guides on the ClojureScript (not sure if the situation is different for Clojure for other applications)
I do think it be nice to have more guides or documentation around some of the more common frameworks like re-frame
I think it's different on Clojure. ClojureScript by virtue of being front end specific, there's a lot of reliance on tooling that could benefit from better docs
And they hear crickets. So maybe they pick up Luminus. Now they ask, okay, where's the documentation? Where are the guides for using Luminus to build a web app?
People say... Sorry, we don't have frameworks. You need to learn the language, and then learn to put together your own custom built framework of choice.
some videos on youtube are helpful for learning how to get a headstart, maybe we could make some videos to onboard newbies fast
this am a repl, this am a lein, etc.
Ya, I've been thinking of trying to start doing video tutorials. Because my attempt at writing a learning Clojure series of blog posts has stalled. There's just too much to teach, writing it is a huge effort, you easily need a book full
But, to go back to my point before. I think there's an expectation mismatch as well. At least for some "newbies". Like they come thinking, hey I heard Clojure is super productive, I heard you can build things things in it quickly, I heard it beats the average. Okay, I'm trying to build a small app, and it's not quick, I am not productive, and nothing shows me how to quickly do it.
If you look at Rails or Django, there's a CLI, you run a command, it scaffolds a whole app, creates your DB tables, schema, creates a homepage, setups admin panels, and all that. One command
No? Okay, go learn it, you'll need too in order to setup Ring with the proper middlewares.
My point is, Clojure approaches things from "first principles". It does so in a way that is immensely productive. And once you know things that way, sky is the limit, this is why you could then "beat the average". And this is why there is no framework everyone uses, because each person is empowered in a way they can build each app from scratch, and put together the exact framework required for the use case.
But this approach to app building is not one that lets you get started quickly, or put things together from tutorials where you copy/paste bits and pieces. It demands much more initial investment before you see results.
also there's frequently a conflict between matching the domain and matching the universal patterns of the language, and clojure often leans toward the latter
which is frustrating when you are new to clojure and know the domain well
but it's awesome when you've learned clojure and now need to approach new domains
I agree. I think there is a huge benefit in knowing and being able to build a minimal web app with just Ring and looking at the request/response abstraction there -- "Just Data" -- and seeing how you layer everything on top with "Just Functions" (e.g., middleware, handlers). It's immensely powerful to see how simple things can be -- and how all these huge, complex frameworks are really not needed for a lot of basic stuff: they obscure "how things work". Clojure really values simplicity over ease of use.
Yup, what I don't know is.. what to do to motivate people to stick around until that "light bulb" moment. I think web app might just be the wrong thing to start with for learning Clojure. I think starting to play around with Quil, or some command line apps might be better. Cause you'll get that reward of accomplishment sooner.
I really wish Clojure had a Django/Rails/Phoenix framework, but it seems like the community has too many smart people with too many opinions 🙂
it’s so much easier to spend the time and effort getting deep, slowly digging towards first principles, when you have a working app in front of you to hack on
Those are also all frameworks primarily concerned with displaying web pages to people. My experience with clojure is it tends to get used for middleware, data processing, apis.
A former coworker liked to call rails on the front and clojure in the back "the mullet"
Hey guys beginner here. I would like to ask what is the best way to have a function have two different behaviors depending on the value of an argument. for example, how do i write the following applying the DRY principle:
(defn foo [x type]
(if (= type :a)
(-> (type-a-calculation1 x)
(type-a-calculation2)
...
(type-a-calculation20))
(-> (type-b-calculation1 x)
(type-b-calculation2)
...
(type-b-calculation20))))
I'm thinking i could use a bond with-stub but im not sure if thats how it should be.defmulti
and defmethod
seem like a perfect fit for this use case
You haven't repeated yourself, those are distinct and different things, just with a similar structure
thanks @hiredman im still struggling to know when stuff is too DRY or too WET. I guess its fine in this case.
What you are responding to as repetition is the repetitive structure of the code, but the content isn't repetitive
It might be possible to abstract our the structure itself in some cases, but I think that would only make sense for a large volume of uses and a more distinctive structure
@chaow Isn't the best aproach here to use polymorphism?
whats the difference between quote and var quote?
quote is part of a language feature, quotation which involves pass literal data through the compiler / evaluator without change
so i should use quote when i dont want the symb to be evaluated
(wrap-reload #'welcome)
why does wrap reload need a var quote instead of just quote?
shouldn't welcome simply be a symbol?
or a form
You want a Var here so you can eval updates to the welcome
function and they'll take effect without needing to restart your app.
Without the var quote, welcome
will be evaluated (to a specific function implementation) when the wrap-reload
call is evaluated and so you would be bound to whatever implementation that function has at the time.
Using a Var adds a layer of indirection, so that when the middleware calls the function at runtime, it'll go through the Var to whatever the current implementation of that function is.
this has something to do with state?
This allows you to develop against a REPL running the application, without needing to stop and restart the application.
or am i mixing stuff here?
"state" in the sense of what the Var "points to", yes.
Can the same redirection be done through a macro which internally handles the indirection?
@hindol.adhya sure, (var welcome)
which does the same thing #'welcome
does
but that's less flexible - what if your handler should be a ref? some other thing which can act as a function but with the reloading semantics (or lack thereof) that you prefer?
is it possible to do partial for functions taking dicts as arg? if i have:
(defn foo [{x :x y :y} :as xy]
(+ x y))
and I wanted to do a partial that specified the :x argument
that's not possible because x isn't an argument, it's a programmatic extraction inside the function - but you can simulate it with merge
(defn foo-x [x] #(foo merge {:x x} %))
for example
there's no partial syntax for it though
I missed the convo but just in case someone is looking for a great little beginner friendly tutorial on building a webapp with the oft recommended ring/compojure/hiccup route I've found this an excellent guide so far: https://practicalli.github.io/clojure-webapps/
@jr0cket's whole site and material out there seem to be top notch and beginner friendly
is there a way to check if a function is a transducer (aside from trying to transduce with it?
There is certainly no transducer?
predicate function in Clojure. I suppose transducers could have been implemented in such a way to make implementing such a thing possible, but that was not how they were implemented.
I have the following function:
(defn exit-with-status [status]
(System/exit status))
I want to test the code that calls this function to ensure that it exits abnormally when certain conditions are met. How can I write a test that will pass only when the argument to my exit-with-status
function is 1, but without causing System/exit to actually get invoked (which will not be good for my test suite)?you could potentially start a new process and check to make sure it actually exits with the correct status code
use with-redefs to redefine the exit-with-status function to be a function that just puts the value in an atom, then check the atom
personally I prefer to throw an exception rather than call System/exit most of the time
that's fair - in this case I'm writing something specifically to be used on the command line so I figured the error codes were the path of least resistance as it were
Note that System/exit will kill your REPL
if your main thread throws an exception then you'll get an error code at the cli. but you're right that you can't give specific error codes I think
bash-3.2$ clj -e '(throw (Exception. "foo"))' || echo FAIL
Execution error at user/eval1 (REPL:1).
foo
Full report at:
/var/folders/p5/xbjgfz0s0sv46kft1j_6gx180000gp/T/clojure-7506154037812967249.edn
FAIL
you could throw in exception in the general case and just catch the exception in your -main
for your cli program to produce the appropriate error code
which may make testing/developing easier
you could even throw ex-infos with a general :cli-error-code for the top level catch to return
it’s nice to keep things like System/exit
near the “outside” of your program
right; it's only invoked from my main method where any relevant errors are resulting in termination
Can I, in the context of my test, rebind the exit-with-status function to some function that passes my test if its argument is 1?
Yes, mark it with ^:dynamic and use binding
(defn ^:dynamic exit-with-status ...)
(defn exit-with-status-FAKE ...)
(binding [exit-with-status exit-with-status-FAKE] ... )
there's also this trick: (let [result (promise) exit-with-status-fake (fn [x] (deliver result x))] ...)
- you can use the state of the promise ot see if a value was delivered, and what value was delivered
without using an atom which is a more general and powerful feature