Fork me on GitHub
#beginners
<
2020-04-22
>
tjb01:04:22

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

tjb01:04:51

perhaps this is wrong to think about?

tjb02:04:39

OR would these be separate database calls and i construct the map myself?

seancorfield02:04:50

@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.

tjb02:04:13

gotcha so it is on me to construct the map then correct?

seancorfield02:04:35

Not sure what "map" you mean. next.jdbc already gives you a hash map.

tjb02:04:42

sorry let me example

tjb02:04:10

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"}

tjb02:04:14

which is a flat map

tjb02:04:03

however i was going to hope i could get this to return like

{
  first: ""
  last: ""
  address: {
    id: ""
  }
}

tjb02:04:07

does that make sense?

tjb02:04:22

maybe im too use to ORMs doing this for me

seancorfield02:04:25

Keys are strings? Oh, you're converting to JSON?

tjb02:04:47

this is the response from my controller

seancorfield02:04:03

So you want to convert the idiomatic internal Clojure format into a specific nested format to return to your clients?

tjb02:04:10

correct!!!

tjb02:04:30

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

seancorfield02:04:34

It's just Clojure data. Transform it however you need to get the format that you want the API to return.

tjb02:04:42

ok that makes sense

seancorfield02:04:50

(I would not expect the API format to match the database format anyway)

👍 4
tjb02:04:32

is this normal in professional clojure dev too

tjb02:04:46

to manipulate the database response into whatever format and then return that to the client?

tjb02:04:19

since everything in clojure is Just Data™

seancorfield02:04:20

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).

👍 4
tjb02:04:10

this is very interesting

tjb02:04:37

from my experience in the java world your domain models mimic how your database is set up

tjb02:04:45

thank you again for the insight @seancorfield!

seancorfield02:04:38

Yeah, that's why ORMs are so terrible.

tjb02:04:51

i def can start to see pros/cons to both

tjb02:04:57

pros to ORMs -- less coding

tjb02:04:10

cons: very rigid

tjb02:04:20

pros to no ORM: flexibility

tjb02:04:27

cons: i have to write more code 😛

seancorfield02:04:25

I've used a wide range of ORMs over a couple of decades and I've hated every single one 🙂

tjb02:04:16

bookmarked for later!

tjb02:04:16

im going to toy around with defrecord as that may help with what i need

tjb02:04:30

but i think i just need to write a custom mapper to map the db response into the shape i want

seancorfield02:04:48

No. Resist. You don't need records. Unless you want type-based dispatch.

tjb02:04:11

i will resist then 🙂 i probably do not need it

tjb02:04:39

ill go with the custom mapper option

seancorfield02:04:24

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...

tjb02:04:06

hmmm do you have an example by chance?

seancorfield02:04:27

(reduce-kv (fn [m k v] (assoc-in m [(namespace k) (name k)] v)) {} results)

👍 4
seancorfield02:04:00

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.

tjb02:04:38

results would be the data returned from the DB correct?

seancorfield02:04:47

Would be each row, yes.

seancorfield02:04:19

So then you could do (assoc (:user data) :address (:address data)) to get your nested map.

tjb02:04:33

oh wow holy cow

tjb02:04:35

this is amazing

tjb02:04:47

giving this a go right now

seancorfield02:04:37

If you had a bunch of things joined and thus multiple nested things, you could do (merge (dissoc data :user) (:user data))

tjb02:04:54

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":

tjb02:04:59

can they be converted somehow?

seancorfield02:04:43

next.jdbc returns them as keywords.

tjb02:04:45

wait im sorry this is what prints

tjb02:04:47

{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}}

tjb02:04:57

from the database using your reduce-kv

tjb02:04:07

how would i get them to be :address unless this is the same thing?

seancorfield02:04:28

Looks like you handed the JSON to the reduce-kv instead of the database result?

tjb02:04:41

(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))))

tjb02:04:57

repo/get-user is

tjb02:04:59

(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]))

seancorfield02:04:53

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.

tjb02:04:10

ah! ok ok ok

seancorfield02:04:50

> So then you could do `(assoc (data "user") "address" (data "address"))` to get your nested map. ^ that will give you almost your JSON

seancorfield02:04:17

Or (reduce-kv (fn [m k v] (assoc-in m [(keyword (namespace k)) (keyword (name k))] v)) {} results)

seancorfield02:04:33

That will produce the keyword-based versions instead of strings.

seancorfield02:04:11

(that's what I get from coding in my head instead of in the REPL 🙂 )

tjb02:04:03

ok very cool!

tjb02:04:10

now i have two nested objects

tjb02:04:17

{"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"}}

seancorfield02:04:48

The nice things about Clojure's "data first" approach is that you can slice and dice this any way you want.

tjb03:04:02

allllmost there

tjb03:04:03

(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})))

tjb03:04:06

(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})))

tjb03:04:17

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"}}

tjb03:04:21

which is what i was looking for 🙂

tjb03:04:31

lets gooooo!!! thank you again Sean!!

seancorfield03:04:02

Why the rename? You don't have :app_user in that hash map.

seancorfield03:04:39

Also, you can use (:app_user data) and (:address data) -- that's more idiomatic when you have known keywords @tjb

tjb03:04:44

i do unfortunately due to postgres

tjb03:04:47

"app_user":{"first":"Yusuke","email":"","id":"c0c03eff-9870-4c0e-83ac-506bf59d69cf","last":"Urameshi"}

seancorfield03:04:08

No, (:app_user data) won't contain that key.

seancorfield03:04:23

And then you assoc in :address

tjb03:04:00

(assoc (:app_user data) :address (:address data))

tjb03:04:09

wont this put :address into :app_user?

tjb03:04:35

(set/rename-keys (assoc (:app_user data) :address (:address data)) {:app_user :user}) to rename :app_user to :user

seancorfield03:04:42

data is {:app_user stuff1 :address stuff2}

seancorfield03:04:21

so (assoc (:app_user data) :address (:address data)) will give you stuff1 with a new key :address whose value is stuff2

tjb03:04:03

right and im renaming {:app_user { :address { ... } } to {:user { :address { ... } }

tjb03:04:10

after that transformation

seancorfield03:04:23

But it's not in that data.

seancorfield03:04:37

Look at your result -- it doesn't contain user.

seancorfield03:04:51

There's no :app_user in that structure.

tjb03:04:57

oh i see what you are saying

tjb03:04:08

yes but this is ideal since i want the top level object to be the user

seancorfield03:04:17

:app_user is in data, but that's not what you're assoc-ing into.

tjb03:04:24

{ :first "" :last "" :address { ... } } is ok

seancorfield03:04:39

And that's what you'll have -- without the rename

tjb03:04:05

good catch thank you sean

tjb03:04:18

i have a final question for the channel

tjb03:04:33

has anyone encountered having to convert kebab case json to camelCase for their client?

dpsutton03:04:33

lol channel = sean

💯 4
4
seancorfield03:04:42

And you Dan 🙂

tjb03:04:43

lolol i def dont wanan bug sean and want to ask everyone

dpsutton03:04:44

(right now at least)

tjb03:04:56

although sean is answering 🙂

seancorfield03:04:07

There's a great library called camel-snake-kebab for converting names.

tjb03:04:36

i saw that lib was am confused where it would live

tjb03:04:48

ideally i dont want to put that on each function i create

seancorfield03:04:57

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

tjb03:04:04

i currently have a function that wraps responses like this

tjb03:04:07

(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])))

dpsutton03:04:11

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

seancorfield03:04:28

Middleware could automatically transform all inbound and outbound keys for you...

dpsutton03:04:11

and then deciding if things will be strings or keywords. can get a bit dicey

tjb03:04:25

so at the DB repo layer you are doing the conversion?

dpsutton03:04:40

right before going in and right after coming out yes

tjb03:04:41

when the data comes out of the database

tjb03:04:17

does that mean you have to wrap each function you create to convert that data?

seancorfield03:04:25

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).

dpsutton03:04:13

do yall tack on question marks to question-y columns? :state "cancelled" type things, do those become keywords?

seancorfield03:04:32

I try to avoid ? on keywords.

dpsutton03:04:51

ah right, you prefer that for predicate functions right

dpsutton03:04:25

(sorry didn't mean to hijack your question @tjb)

seancorfield03:04:26

I'm on the fence about ? on local symbols. Some days I feel more favorable to it than others 🙂

tjb03:04:30

i am listening in and learning 🙂

seancorfield03:04:43

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).

seancorfield03:04:39

But now I'm more inclined to let :snake_case leak out of the DB layer and into the surrounding code...

seancorfield03:04:48

It's fewer moving parts at the expense of some slightly less idiomatic keyword names.

tjb04:04:10

woot! got some middleware workin

tjb04:04:14

for the curious it looks like this

tjb04:04:32

(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])))

tjb04:04:00

i was looking at ring source code so i dont fully understand stuff yet but it works...

pinkfrog04:04:28

it seems to me, (def a (atom some-non-persistent-java-container)) is totally meaningless.

pinkfrog04:04:56

atom is only valid for persistent data structures.

andy.fingerhut05:04:56

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.

andy.fingerhut05:04:29

The intended use case is to hold immutable values inside of atoms, and most Clojure mutable reference types.

seancorfield04:04:09

@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.

seancorfield04:04:58

And of course now you have middleware, you could auto-convert your input parameters (:params request) to kebab-case as well.

‼️ 4
Umar Daraz06:04:52

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))

kelveden08:04:42

i.e. integrant.repl.state/system

Umar Daraz08:04:49

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!

👍 4
Aron09:04:24

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 🙂

Aron09:04:35

right now all I get is a big line

Aron09:04:01

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

Chris McCormick09:04:48

@ashnur are you developing in the browser?

Aron09:04:34

yup, shadow-cljs

Aron09:04:05

I know I could do clj->js and use JSON.stringify but I would like a less indirect solution 😄

Chris McCormick09:04:31

you could also try (js/console.log (clj->js val)) and then take a look in the browser console

Aron09:04:22

I guess my previous line is ignored then?

Aron09:04:31

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

Aron10:04:48

https://github.com/yogthos/json-html this works more or less, although it was a bit of a struggle still 🙂

sogaiu10:04:02

is it the case that you would rather not look at the browser console?

Chris McCormick10:04:22

> and use JSON.stringify @ashnur i mean use the console without doing this

sogaiu10:04:40

i ask because i did get some use out of: https://github.com/binaryage/cljs-devtools -- it just requires looking at the browser console

😍 4
☝️ 4
sogaiu10:04:29

i guess it implies a certain browser-type as well

sogaiu10:04:25

there's also a hard-core version by the same author (https://github.com/binaryage/dirac)

sogaiu10:04:07

the latter can be a bit much to set up and keep up-to-date iirc though

Aron10:04:38

@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

sogaiu10:04:22

good to hear you found something that works -- it wasn't clear to me from your "more or less"

Aron10:04:30

thanks @sogaiu will try those too, but yeah at the moment I just wanted inline in the browser window

sogaiu10:04:44

good luck 🙂

Aron10:04:56

well, it's not quite what the screenshots are displaying, but it gets the job done

Jose Ferreira15:04:00

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?

Jose Ferreira15:04:59

Everything i found seems kinda outdated, books from 5 to 10 years ago. Would they still be relevant today?

manutter5115:04:19

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.

manutter5115:04:08

Clojure for the Brave and True is frequently recommended for beginning Clojure https://www.braveclojure.com/

8
manutter5115:04:07

And there’s also a #braveandtrue channel if you have questions

Jose Ferreira15:04:06

Is that also true for the clojure script enviroment?

Jose Ferreira15:04:34

I've been through most of brave and throught and Programming Clojure

Jose Ferreira15:04:09

I was looking more for a book that could give me an overview of how to really build something

manutter5115:04:01

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

Jose Ferreira15:04:06

when i say most, that might be an overstatement, (i finished the basic stuff)

manutter5115:04:09

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

Jose Ferreira15:04:27

Exactly, but what would be good resources for finding the right tools? How to i build the dev stack?

Jose Ferreira15:04:02

All of this seems very different from what i'm used to...:(

Jose Ferreira15:04:29

There's no framework that gets everything you need right?

Jose Ferreira15:04:41

Something like django or laravel

manutter5115:04:28

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

manutter5115:04:11

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.

manutter5115:04:26

You might also want to look at Sean Corfeld’s sample library at https://github.com/seancorfield/usermanager-example

Jose Ferreira15:04:53

tks a lot, i'll be looking into that

Jose Ferreira15:04:42

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?

Jose Ferreira15:04:51

i was looking at web dev with clojure

manutter5115:04:37

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.

Jose Ferreira15:04:20

OK, i'll try to build something basic with compojure and ring, and see how that goes...^^

Jose Ferreira15:04:48

but what would be the big difference between compojure and reitit?

Jose Ferreira15:04:09

Is it that one would be better in a specific situation?

seancorfield16:04:12

reitit describes routes as data. Compojure describes routes via code (macros). I think reitit supports bidirectional routing but Compojure does not.

seancorfield16:04:46

@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)

Jose Ferreira16:04:49

tks a lot sean, thats what i feared...

Jose Ferreira16:04:38

im trying something simple for myself now, using just clojure with ring and compojure as was recommended by @manutter51

Jose Ferreira16:04:12

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

Jose Ferreira16:04:21

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

Jose Ferreira16:04:27

and are very oppinated...

Jose Ferreira16:04:38

i guess thats just not the clojure way

andy.fingerhut16:04:44

There is a recent 3rd edition of web dev for Clojure, much more recent than 2012.

seancorfield16:04:58

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).

Jose Ferreira16:04:15

it seems its not out yet thou

seancorfield16:04:41

You can buy the preview/beta ebook and get updates as they come out.

seancorfield16:04:58

(that's what I did)

Jose Ferreira16:04:34

i'm old school in that regard, i like to have my books in physical form

Jose Ferreira16:04:06

Do you recommended it?

seancorfield16:04:12

Last update was March 25th. Beta 7 of the ebook.

Jose Ferreira16:04:16

if you've read it already...

seancorfield16:04:15

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.

seancorfield16:04:37

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 🙂

Jose Ferreira16:04:29

shameless plug....xD

Jose Ferreira16:04:07

but yes, i guess i understand the choice...show you the cappabilities, and then explain it in more detail

Jose Ferreira16:04:15

top down aproach

seancorfield16:04:48

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.

8
Jose Ferreira16:04:57

ok, selmer seems pretty straightforward, just a template system....

Jose Ferreira16:04:11

component seems like a more alien concept...

sova-soars-the-sora16:04:18

What kind of thing are you trying to build? (=

Jose Ferreira16:04:40

nothing in particular right now...just dabbling

Jose Ferreira16:04:57

i saw a few talks about this and got curious

Jose Ferreira16:04:03

just managed to say hello world to the browser,

Jose Ferreira16:04:42

lets hope it gets a little bit more exciting than this

sova-soars-the-sora16:04:47

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.

sova-soars-the-sora16:04:58

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.

Jose Ferreira16:04:00

i like how everything is very modular around here...

sova-soars-the-sora16:04:33

I would hang my stomach up on the wall while not using it if I could xD

Jose Ferreira16:04:46

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)

theequalizer7316:04:49

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

sova-soars-the-sora16:04:32

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

Mario C.16:04:43

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)))

Jose Ferreira16:04:36

@orlandomr27 {:status 200 :body "Olha a cara do bis!" :headers {}})

theequalizer7316:04:47

the register-and-login branch has more features. I’m working on register form

Jose Ferreira17:04:14

yeah, i'm looking through

Jose Ferreira17:04:20

So this is basically a book DB right?

Jose Ferreira17:04:51

what are you using as your learning resources?

theequalizer7317:04:03

a lot of things but mainly Adam Bard’s blog and video about web app in Clojure. http://PurelyFunctional.tv and LambdaIsland videos

theequalizer7317:04:18

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

Jose Ferreira17:04:12

do you have a link for the project?

theequalizer7316:04:19

I’m confortable with spanish and portuguese yes :)

Jose Ferreira16:04:34

@sova yes, but the entry point for someone who starts looking into this seems very convuluted

theequalizer7317:04:23

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.

Jose Ferreira18:04:54

yes, but clojure is not really a new language...

Jose Ferreira18:04:11

neither is LISP, and this has been a problem for all LISPS

seancorfield17:04:27

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.

clj 8
seancorfield17:04:45

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)

👍 4
Jose Ferreira17:04:20

ok, but if you want mass adoption of a programming language, that doesn't seem like a very good strategy

seancorfield17:04:36

Mass adoption is not a goal for Clojure.

😄 4
Jose Ferreira17:04:53

why not?

😅 4
Jose Ferreira17:04:22

wouldn't it be beneficial?

seancorfield17:04:17

See Stuart Halloway's tweets like this https://twitter.com/stuarthalloway/status/1249772079068700674 (he and Rich are the primary stewards of Clojure)

seancorfield17:04:12

I'm curious as to what you think would be beneficial about mass adoption?

Jose Ferreira17:04:01

well, a bigger community would mean more people contributing

Jose Ferreira17:04:26

isn't that what makes languages flourish?

seancorfield17:04:13

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.

👍 4
Jose Ferreira17:04:35

yeah you're right about that one

Jose Ferreira17:04:54

seems like a small but very productive community

Jose Ferreira17:04:34

but mass adoption would also bring more attention, and there would be more jobs

seancorfield17:04:50

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.

👍 4
Jose Ferreira17:04:17

Lisps have been failing to take over the world continusly for decades now

😆 4
🎯 4
seancorfield17:04:06

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 😐

Jose Ferreira17:04:39

but the concepts are awsome...

Jose Ferreira17:04:11

i belive if the entry point wasn't so hard, there would be a lot more people ready to take it

Jose Ferreira17:04:54

and by that, i don't mean lang syntax or semantics

Jose Ferreira17:04:15

i just mean organized content

Jose Ferreira17:04:51

JS is very convuluted also, you have hundreds of libs and frameworks as well

Jose Ferreira17:04:09

but you can find a lot of content, and a lot of guides

seancorfield17:04:49

And a lot of that JS content is quickly outdated because of all the (backward-incompatible) churn in libraries and frameworks etc 🙂

seancorfield17:04:40

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!

16
seancorfield17:04:55

I find Clojure's ecosystem to be a model of simplicity and stability by contrast.

4
seancorfield17:04:35

Occasionally I have to write a bit of JS and every time it is an exercise in frustration for me!

seancorfield17:04:54

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).

4
seancorfield17:04:49

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.

seancorfield17:04:18

(which is all my way of saying "don't get discouraged -- this is hard and we all know and accept that" 🙂 )

Jose Ferreira17:04:40

i totally agree with you, but you're missing my point (or i wasn't able to get it across correctly)

Jose Ferreira17:04:38

it's not the syntax or the philosophy of the language that i'm "complaining" about

Jose Ferreira17:04:06

i get that it's different, i had a few prior experiences with lisps before

Jose Ferreira17:04:52

its just that there are very few beginner content...

didibus18:04:58

You say that, but having been hanging around helping beginners for a while, I think the issue IS having to learn unfamiliar concepts

Jose Ferreira18:04:17

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

didibus18:04:27

Well, what I see is people get the impression that their struggle is due to the lack of "easy to digest" tutorial and guides

didibus18:04:30

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

didibus18:04:33

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

didibus18:04:28

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.

didibus18:04:56

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.

didibus18:04:20

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.

didibus18:04:21

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

didibus18:04:47

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.

didibus18:04:32

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.

didibus18:04:57

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.

Jose Ferreira19:04:35

yeah, i get what you-re saying

Jose Ferreira19:04:06

i was also comming to clojure with the wrong mindset, looking for a structered framework

Jose Ferreira19:04:15

where everything was setup for me

Jose Ferreira19:04:35

i-m starting to get that the environment itself is very different

👍 4
Jose Ferreira19:04:09

i'll tackle on, eventualy all those little things will become second nature

❤️ 4
Jose Ferreira19:04:48

the truth is, this is harder, but its also better. it forces you to learn about all the little things

Jose Ferreira19:04:06

that are done automaggicallyby framworks like django or laravel

didibus19:04:23

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.

Jose Ferreira19:04:08

yes, it might not even be needed... but that is the norm form people comming from other paradigms

Jose Ferreira19:04:18

so thats the first thing they'll be looking for

didibus19:04:19

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:

Jose Ferreira19:04:36

I just think that needs to be explicit

Jose Ferreira19:04:24

exactly as you were saying before...it his a matter of expectation

didibus19:04:37

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

didibus19:04:32

And we don't want to send the wrong message either

Jose Ferreira17:04:14

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

Jose Ferreira17:04:35

There are very concise guides and explanations

seancorfield17:04:27

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.

seancorfield17:04:33

(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)

seancorfield17:04:47

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

Jose Ferreira17:04:40

yeah, you're right, i guess i was just a bit frustrated for trying to figure this stuff out for 2 days...:)

Jose Ferreira17:04:42

I'll keep hacking on, and when i get this stuff out maybe i'll try to add my 2cents to help others start

Patrick Truong18:04:44

@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

4
Jose Ferreira18:04:27

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

Jose Ferreira18:04:30

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

🙂 4
4
Patrick Truong18:04:54

@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

Mario C.17:04:42

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

8
noisesmith18:04:01

that mostly works, but avoid things like Spring :D

seancorfield18:04:27

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 👀

theequalizer7318:04:15

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 😁

Patrick Truong18:04:31

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 🙂

seancorfield18:04:22

@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.

Patrick Truong18:04:31

@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 🙂

seancorfield19:04:21

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.

👍 4
Ben Sless18:04:52

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?"

Jose Ferreira18:04:42

Scheme is taught as a first language in many uni

Jose Ferreira18:04:48

it was my first language

Jose Ferreira18:04:20

but is mostly presented as something that you cannot work with, something that is purely academic

Jose Ferreira18:04:00

So it tends to be dismissed by most students, from my experience

Jose Ferreira18:04:38

Well thanks a lot for your input guys, i have to leave...

andy.fingerhut18:04:14

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.

andy.fingerhut18:04:25

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.

didibus18:04:18

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?

Patrick Truong18:04:59

@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)

didibus08:04:16

I do think it be nice to have more guides or documentation around some of the more common frameworks like re-frame

didibus08:04:08

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

👍 4
didibus18:04:10

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?

didibus18:04:35

And they hear crickets 😋

didibus18:04:30

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.

sova-soars-the-sora18:04:06

some videos on youtube are helpful for learning how to get a headstart, maybe we could make some videos to onboard newbies fast

4
sova-soars-the-sora18:04:23

this am a repl, this am a lein, etc.

didibus18:04:14

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

4
didibus18:04:02

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.

didibus18:04:24

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

didibus18:04:12

In Clojure it's like... Okay, do you know the HTTP spec? 😝

didibus18:04:41

No? Okay, go learn it, you'll need too in order to setup Ring with the proper middlewares.

didibus18:04:59

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.

didibus19:04:09

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.

noisesmith19:04:12

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

👍 4
noisesmith19:04:23

which is frustrating when you are new to clojure and know the domain well

noisesmith19:04:47

but it's awesome when you've learned clojure and now need to approach new domains

seancorfield19:04:52

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.

👍 16
didibus19:04:40

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.

😮 4
kenj19:04:58

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 🙂

kenj19:04:50

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

hiredman19:04:45

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.

hiredman19:04:58

A former coworker liked to call rails on the front and clojure in the back "the mullet"

😛 12
chaow19:04:48

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.

phronmophobic19:04:46

defmulti and defmethod seem like a perfect fit for this use case

hiredman19:04:17

1. DRY is meh 2. That is DRY

hiredman19:04:57

You haven't repeated yourself, those are distinct and different things, just with a similar structure

chaow19:04:08

thanks @hiredman im still struggling to know when stuff is too DRY or too WET. I guess its fine in this case.

hiredman19:04:44

What you are responding to as repetition is the repetitive structure of the code, but the content isn't repetitive

hiredman19:04:33

All limericks are not repititions even though they have the same structure

🙈 4
🎓 4
Michael J Dorian19:04:07

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

Jose Ferreira19:04:24

@chaow Isn't the best aproach here to use polymorphism?

Jose Ferreira20:04:49

whats the difference between quote and var quote?

hiredman20:04:27

they are entirely different

hiredman20:04:58

var quote is really just part of the syntax for writing a literal var

hiredman20:04:16

quote is part of a language feature, quotation which involves pass literal data through the compiler / evaluator without change

Jose Ferreira20:04:55

so i should use quote when i dont want the symb to be evaluated

Jose Ferreira20:04:36

(wrap-reload #'welcome) why does wrap reload need a var quote instead of just quote?

Jose Ferreira20:04:46

shouldn't welcome simply be a symbol?

seancorfield20:04:38

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.

seancorfield20:04:25

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.

seancorfield20:04:16

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.

Jose Ferreira20:04:35

this has something to do with state?

seancorfield20:04:45

This allows you to develop against a REPL running the application, without needing to stop and restart the application.

Jose Ferreira20:04:46

or am i mixing stuff here?

seancorfield20:04:02

"state" in the sense of what the Var "points to", yes.

hindol21:04:54

Can the same redirection be done through a macro which internally handles the indirection?

noisesmith21:04:53

which does the same thing #'welcome does

noisesmith21:04:42

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?

chaow21:04:46

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

noisesmith21:04:28

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

noisesmith21:04:21

(defn foo-x [x] #(foo merge {:x x} %)) for example

noisesmith21:04:28

there's no partial syntax for it though

Chase21:04:25

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/

Chase21:04:48

@jr0cket's whole site and material out there seem to be top notch and beginner friendly

👍 4
Akshay C. Gollapalli22:04:35

is there a way to check if a function is a transducer (aside from trying to transduce with it?

andy.fingerhut22:04:19

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.

mister_m23:04:18

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)?

phronmophobic23:04:11

you could potentially start a new process and check to make sure it actually exits with the correct status code

bfabry23:04:45

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

bfabry23:04:02

ie, use with-redefs in the test

mister_m23:04:53

oh I haven't read about atoms yet, now I have an excuse

bfabry23:04:20

personally I prefer to throw an exception rather than call System/exit most of the time

mister_m23:04:02

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

nikolavojicic23:04:12

Note that System/exit will kill your REPL

mister_m23:04:27

right, that is why I need my tests to not call it

bfabry23:04:47

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

bfabry23:04:38

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

bfabry23:04:12

vs

bash-3.2$ clj -e '(println "foo")' || echo FAIL
foo
bash-3.2$

phronmophobic23:04:01

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

phronmophobic23:04:17

which may make testing/developing easier

bfabry23:04:54

you could even throw ex-infos with a general :cli-error-code for the top level catch to return

phronmophobic23:04:56

it’s nice to keep things like System/exit near the “outside” of your program

mister_m23:04:32

right; it's only invoked from my main method where any relevant errors are resulting in termination

mister_m23:04:13

I'm using clojure.test as well.

mister_m23:04:07

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?

nikolavojicic23:04:05

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] ... )

noisesmith23:04:52

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

noisesmith23:04:06

without using an atom which is a more general and powerful feature