Fork me on GitHub
#clojure
<
2021-06-19
>
zendevil.eth06:06:35

Is there no better/more concise built-in way to access the i,j element of a 2d vector than (nth (nth v i) j)?

joshkh06:06:49

just spitballing here:

(nth (nth [[:a :b]
           [:c :d]] 0) 1)
=> :b

(nfirst [[:a :b]
         [:c :d]])
=> (:b)

joshkh06:06:16

ah, that applied before your edit 🙂

delaguardo08:06:42

(get-in v [i j])

💯 2
quoll11:06:28

Much more concise, but slower

delaguardo12:06:32

then how about (reduce nth v [i j]) ? )

delaguardo12:06:03

still slower, but not an order of magnitude )

vemv12:06:36

(-> v (nth i) (nth j)) Readable and no runtime impact

Alex Miller (Clojure team)13:06:42

I think we actually have an enhancement ticket for this

zendevil.eth06:06:39

If the current implementation of get-in is order of magnitudes slower than recursively calling nth, then why not have get-in return (reduce nth v [i j]) when the first argument is a vector?

delaguardo06:06:50

https://groups.google.com/g/clojure/c/apkNXk08Xes/m/CGCQqLMhlHwJ This is about different function but the problem is the same I think.

zendevil.eth09:06:35

It would be bad to have a slower worst case complexity than promised for a polymorphic function but it makes no sense to not give a better worst case complexity while promising the worst for types for which it can be done.

zendevil.eth09:06:57

Actually:

user=> (time (reduce nth (mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000)) [1 5]))
"Elapsed time: 8390.244325 msecs"
:w
user=> (time (get-in (mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000)) [1 5]))
"Elapsed time: 5070.179514 msecs"
:w
@U051N6TTC upon checking, get-in performs better than recursive nth’s.

💯 2
delaguardo09:06:30

(let [v (mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000))]
  (time (dotimes [_ 1000000]
          (reduce nth v [1 5])))
  (time (dotimes [_ 1000000]
          (get-in v [1 5]))))
this one is actually testing performance of reduce nth vs get-in

delaguardo09:06:40

"Elapsed time: 84.604412 msecs"
"Elapsed time: 115.409083 msecs"

zendevil.eth10:06:15

Why is this the case?:

user=> (time (dotimes [_ 2]
     (reduce nth (mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000)) [1 5])))
"Elapsed time: 12118.690123 msecs"
nil

user=> (let [v (mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000))] 
(time (dotimes [_ 2]
     (reduce nth v [1 5]))))
"Elapsed time: 0.137276 msecs"
nil

delaguardo10:06:57

because most of the time your code is spending on creating vector of vectors

delaguardo10:06:49

(mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000)) that form to be specific

zendevil.eth10:06:27

But they should be t * 2 apart, not 10^6 apart.

zendevil.eth10:06:13

user=> (time (dotimes [_ 1] (reduce nth (mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000)) [1 5])))
"Elapsed time: 5329.495202 msecs"

user=> (let [v (mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000))] 
(time (dotimes [_ 1]
          (reduce nth v [1 5]))))
"Elapsed time: 0.125426 msecs"

delaguardo10:06:19

because generation of vector of vectors is taking ~6 sec (mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000))

zendevil.eth10:06:30

user=> (time (reduce nth (mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000)) [1 5]))
"Elapsed time: 4676.386932 msecs"
:w
user=> (let [v (mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000))] 
(time 
          (reduce nth v [1 5])))
"Elapsed time: 0.026209 msecs"
:w
wtf

delaguardo10:06:28

what precisely surprising you? )

zendevil.eth10:06:35

I had meant:

user=> (time (let [v (mapv #(vec (repeat 20000 ({1 :w 6 :b} %))) (range 20000))] 
(reduce nth v [1 5])))
"Elapsed time: 4791.224092 msecs"
:w
in the second case.

delaguardo10:06:27

the process of building collection is significantly much more expensive operation than reduce nth or get-in that’s why you see almost the same results (if that is the thing you concern about).

delaguardo10:06:13

this is why in my example building of v is done outside of time

zendevil.eth10:06:33

But reduce nth is an order of magnitude faster than get-in, therefore for vector inputs get-in should simply call reduce nth

delaguardo10:06:36

should doesn’t mean it will ) there are other thing you should consider before making get-in works differently for vectors and maps.

zendevil.eth10:06:37

Also I didn’t know that let bindings do the computation where they are defined and don’t wait until the bindings are used within the let

zendevil.eth10:06:09

@U04V4KLKC backwards compatibility is a factor I suppose.

zendevil.eth10:06:18

How about this:

(defn get-in-candidate
  "Returns the value in a nested associative structure,
  where ks is a sequence of keys. Returns nil if the key
  is not present, or the not-found value if supplied."
  {:added "1.2"
   :static true}
  ([m ks]
   (if (vector? m)
     (try (reduce nth m ks)
          (catch Exception _ nil))
     (reduce1 get m ks)))
  ([m ks not-found]
   (if (vector? m)
     (try (reduce nth m ks)
          (catch Exception _ not-found))
     (loop [sentinel (Object.)
          m m
          ks (seq ks)]
     (if ks
       (let [m (get m (first ks) sentinel)]
         (if (identical? sentinel m)
           not-found
           (recur sentinel m (next ks))))
       m)))))

delaguardo11:06:13

You introducing potentially less performance variant because now it is using vector? and try/catch

delaguardo11:06:19

Plus I'm not sure this is backward compatible change

chrisn16:06:27

https://cnuernber.github.io/dtype-next/tech.v3.tensor.html is designed for nd operations. An array of arrays or a vector of vectors is a poor representation for generic ND objects for various reasons and these really cannot be improved by too much.

zendevil.eth06:06:16

Is there a better/more concise way to do this:

(mapv
                (fn [row]
                  (mapv (fn [i] (cond (= row 1) :w
                                     (= row 6) :b))
                        (range 8)))
                (range 8))

indy07:06:36

(for [row (range 0 8)]
  (for [col (range 0 8)]
    (case row
      6 :b
      1 :w
      nil)))

indy08:06:14

Wish there was a forv . But there would've been a reason for that not being there. For vector of vectors (not as pretty as the first).

(vec (for [row (range 0 8)]
        (vec (for [col (range 0 8)]
               (case row
                 6 :b
                 1 :w
                 nil)))))

quoll11:06:50

“Better” is subjective. I like code that maps to how I’m thinking about what’s happening. Sometimes that’s less concise code that’s clearer. Sometimes it’s more concise. Occasionally I opt for code that’s faster to execute, but that isn’t always necessary. Anyway, since the value is identical throughout the row, I’d rather see the selection outside the inner loop. And because it’s the same character, then I don’t see the need for an explicit loop. So my preference for faster/concise code here is:

(mapv #(vec (repeat 8 ({6 :b 1 :w} %))) (range 8))

3
zendevil.eth06:06:30

What’s bugging me with the above two code snippets is that it’s possible and even idiomatic to achieve each in Python with less characters/loc’s: v[i][j]

boardRow = [None] * 8
board = [boardRow.copy() for i in range(8)]
board[1] = ["w"] * 8
board[6] = ["b"] * 8

delaguardo09:06:31

You could recommend instead of comparing loc try to model you board differently. For example {:size 8 [1 0] "w"...}

jaihindhreddy10:06:57

A direct translation of this would something like this:

(-> (vec (repeat 8 (vec (repeat 8 nil))))
    (assoc 0 (vec (repeat 8 \w)))
    (assoc 6 (vec (repeat 8 \b))))
the way * works with lists in Python is what makes that piece of code more concise than Clojure. By making that a local fn, we can make this a bit more concise:
(let [f #(vec (repeat 8 %))]
  (-> (f (f nil))
      (assoc 0 (f \w))
      (assoc 6 (f \b))))
At this point though, I'd consider this golfing. Clojure might not be the most concise language locally for any given piece of code, but its generality means it is concise in the large, where it (arguably) matters more.

👎 3
joshkh06:06:29

i'm a little rusty when it comes to macros. maybe someone can help? in the following example i'd like the output from:

(defmacro some-macro
  [label]
  '(abc '(~label)))

(some-macro hello)
to be
(abc '(hello))
but i'm having trouble unquoting/splicing in the double quoted label value:
(macroexpand-1 '(some-macro "hello"))
=> (abc (quote ((clojure.core/unquote label))))

nenadalm06:06:37

(defmacro some-macro
  [label]
  `(~'abc (list ~label)))

joshkh07:06:38

thanks @U662GKS3F. i'm getting a syntax error unless i pass in hello as a symbol 'hello

(some-macro hello)
Syntax error compiling at (...form-init6796106587303266535.clj:1:1).
Unable to resolve symbol: hello in this context
any way around that?

joshkh07:06:42

actually, scratch that question 😉

joshkh06:06:10

(defmacro some-macro [label] '(abc '(`~label)))
gets me a little closer but i'm still not quite there

eskos08:06:06

@joshkh Would something like

(def board (mapv
            #(vec (repeat 8 %))
            [nil :w  nil nil nil nil :b nil]))
satisfy your desire? Idea is to model the problem as a repetition of columns which are expanded to rows

zendevil.eth09:06:51

But in this case the board doesn't generalize to any size nxn, whereas we can generalize the previous examples to any size of board by setting n = 8 and replacing all 8’s with n’s

p-himik09:06:25

You can generalize it by replacing the explicit vector with (-> (repeat 8 nil) vec (assoc 1 :w 6 :b)).

joshkh09:06:23

I think the question was from someone else :)

Jakub Šťastný13:06:44

Hey guys, how do people in the Clojure world think about writing web apps? Example: In Ruby people think "what framework is good for my use-case?" first. Many of them go with Rails, which does everything for them (and gets into the way in my opinion, but whatever, this is how the mentality goes). Everybody would use some heavy ORM. If a new service is needed, for instance some queue, it'd always run in a separate process (MRI Ruby has GIL and mutable state, no one uses threading). So then the app would run in an application server and say Nginx would proxy to it. How about Clojure? I'm asking to get the right mindset before I start searching for concrete libraries and tools. Being used to certain ways, it's easy to try to do things the old way rather than ponder what approach suits to this technology. So for instance I might be used to large web frameworks and I start searching for "best web framework clojure", while using a framework might not even be a thing in the Clojure world and maybe library approach is more popular instead? So this is what I would like your help with, to suggest me a mindset I should adopt towards this. I'm suspecting Clojure is not going to take the framework approach as much and likely will use few lighter libraries instead. In that case what are the libraries and how do they work together? I mentioned the process/threading issue, that's another big question I have. Once I was playing with JRuby and it seemed to me that it tries to put everything into the same process and use threading and avoid starting new processes at all cost. Does Clojure take this approach as well? The app I'm going to write is just REST API, no views will be generated on the server.

p-himik13:06:25

> maybe library approach is more popular instead? That is my perception. And, after years of using both approaches across different languages, I can definitely tell that FWIW I myself definitely prefer the library approach. > what are the libraries Often there are many libraries for a single task, e.g. HTTP servers. Sometimes there are articles that describe differences, but sometimes you have to figure it out yourself by reading all the READMEs, API documentation, example code. > how do they work together They don't work together themselves - because they don't even know about each other's existence. You compose them together, and in Clojure it can be done easily. E.g. you need to write a CRUD app. The basic step of the R part is to serve a single object by its ID. So you figure out what kind of API you want, you then implement some basis for that API (e.g. an HTTP handler for /data/{model}/{id}), and then you somehow access your DB there. There are things that help with organization - Integrant, Mount, Component, Clip, maybe others. > Does Clojure take this approach as well? Yes, although you usually don't have to start new threads directly. You can use futures, core.async, agents, other libraries like Manifold. You definitely don't want to start other JVM processes simply because it takes a long time to do that. Even if everything is already compiled, the order of magnitude is seconds.

Jakub Šťastný13:06:47

Thanks @U2FRKM4TW! That sounds good then, I'll try to look into the libraries you mentioned and will take it from there. The threading approach is new to me – all of the languages I used either have GIL (MRI Ruby, Python while back) or are single-threaded (Node.js), so this is very new. Well at least we've got immutability.

p-himik13:06:26

If you used async function with Node or Python, you'll feel right at home with core.async. And, to be fair, Python also has threads and futures, and sometimes they're useful even despite the GIL.

javahippie13:06:18

A good entry-point into exploring web technologies with Clojure might be Luminus. It’s called “Framework” but is more of a templating mechanism to wire the relevant libraries together as a quickstart for web development.

Jakub Šťastný14:06:41

@U2FRKM4TW oh that's good to hear! Well Ruby also has threads (and fibers), but people generally don't use them. Same situation as in Python as far as I know, although it's been a while since I did any Python, so my knowledge might be outdated.

Jakub Šťastný14:06:04

Will check Luminus out, cheers @U0N9SJHCH!

West15:06:57

Maybe a noob question. It seems like a lot of people really like to write clojure webapps by using a server library. Whether that’s with httpkit, jetty, ring, or whatever, you’re hand-rolling a server. What’s the advantage over using something like nginx, apache, or even caddy?

vemv16:06:42

Having done Ruby and Clojure professionally for approximately the same amount of years, I wouldn't say both categories of webapps have drastically different architectural approaches. In fact I keep believing most differences are superficial: * Rails ~= a composition of libraries (which ends up being an ad-hoc framework anyway) * load balancer + app web server with either choice (if you care about slow client attacks and similar reliability concerns) * redis/sqs/... for background jobs (you can use native threads with either lang... I would avoid them almost always because lack of persistence means your jobs can be lost in face of bugs or various incidents) * ActiveRecord is one of the cleanest ORMs in the world, you can see it commonly praised in HN threads or such. It's a honesyql in disguise: it doesn't intend to hide SQL, you can go as low-level as you want while remaining safe. honesyql is nicer as it's plain data, but AR's api also maps closely to SQL in a way that data also would. * Rails apps are architecturally immutable in that they aren't based in global mutable state. Each request/response cycle tends to be isolated.

vemv16:06:19

So, IMO the differences are subtle, and smaller than the differences that would arise from team decisions anyway (e.g. some teams will choose microservices, ORM / no ORM, graphql / no graphql, etc). Clojure's offering is worth the change, but it's not a dramatic one either and when executed poorly you may end up with a less productive, and even less secure ad-hoc framework. For staying on the safe side I'd use templates like those of pedestal, reitit, juxt, luminus. Later as one grows in clj knowledge one could deviate from that

Jakub Šťastný16:06:01

@U01KQ9EGU79 good question, I'm not sure about that one either. I was always using Nginx and being new to Clojure this is not clear to me.

p-himik16:06:18

@U01KQ9EGU79 When using nginx or Apache as a reverse proxy or by extending them with Clojure, you still write exactly the same code (almost the same in the case of extending). The difference is in the configuration. Try writing two simple apps - one that serves a dynamic HTML via Ring without anything else and another via Nginx + Clojure, in whatever way you decide to combine them.

West16:06:43

@U2FRKM4TW Ah, so I guess it’s easier when it comes to configuration. I figured a relatively default nginx server would have better security than writing a server with something like ring.

vemv16:06:09

One practical thing where Clojure / JVM wins massively though is in performance, instrumentation, compatibility with things like Kafka... Ruby's C wrappers and a limited GC often don't quite cut it :)

p-himik16:06:29

@U01KQ9EGU79 Doubt it. If you use it as a reverse proxy, Ring will still be there, behind Nginx. If you embed Clojure into Nginx, you'll probably use nginx-clojure, a third-party library. So again, not that different from Ring in terms of it being yet another entity, but it's definitely less known and less battle-tested. But I'm not a security expect, so don't listen to me.

p-himik16:06:18

@U45T93RA6 Warning: the following comment is mostly a rant. I have never used RoR myself, but I've heard descriptions of some first-hand experience from colleagues whose opinion on the matter I'd trust. The general consensus was that the "on rails" is quite a good analogy - it's great while you're moving in the direction of the rails, but it's godawful if you want to deviate a bit. When it comes to web frameworks, I have extensive experience only with Django. And it's the same there. It's just unfeasible to do anything that Django doesn't offer or do anything in a different way than it does. Even if you don't use its ORM (which is pretty much 70% of Django), it's still the same. While all that I described here transpired years ago, I have doubts that it's in any way different now. Even back then, I remember popular issues with attached patches on Django bug tracker that have been open for 5-8 years.

Jakub Šťastný16:06:17

@U2FRKM4TW I have to say I always hated Rails, but being a Ruby dev, that was what I always had to work with. It's bloody dreadful. Much less so now, but back in Rails 1.2 days, God, configuring that thing was just bloody impossible. Now it's got adapters and you can pick what you want, mostly, but still, I don't consider it very suitable for composition, there's the Rails way and you just have to tag along. I do thing that all the time it could save you you have to pay back when you're trying to debug it. Seriously gives me nightmares. Ruby is a cool language, but Rails makes you want to cry. I'd definitely go the library path now, because I really don't think the frameworks are such a great win as some people think they are.

Jakub Šťastný16:06:57

Anyway better don't get me started on Rails 🙂

emccue16:06:11

FWIW, I think at the end of the day all clojure people agree on ring in one form or another

emccue16:06:50

You get a request object from your web server of choice then a bit of clojure transforms that into a standardly shaped "request map"

emccue16:06:11

and you return a map with :status, :headers, and :body and that is converted into a response

emccue16:06:31

there are some variations on that (like pedestal puts that request map into context part of a larger architecture, compojure will help you write the routing bits, etc) but thats the standard way to handle http requests

Jakub Šťastný16:06:55

Is it like a rough equivalent of WSGI in Python/Rack in Ruby? I will definitely check it out.

emccue16:06:01

yeah kinda

emccue16:06:42

and then beyond that for sql access there have been ORMs written, but the most prevalent approach seems to be to use JDBC directly (via next.jdbc)

emccue16:06:14

either by writing sql in strings, generating that sql with honeysql, or loading sql from files with hugsql

vemv16:06:13

I had to google Rails 1.2 2006... geez :) yeah I heard bad things about that era post-merb merge it always seemed a surprisingly well-architected framework. You can take activemodel, or activesupport and use them with no web framework at all. Or vice versa - you can bring your own ORM. Finally, Rails is also is implemented in terms of Engines which are basically modules. Polylith is exploring that idea which otherwise is oft-forgotten in Clojure architectures

emccue16:06:32

there are multiple more divergent options for configuration, html templating, routing, websockets, logging, database migrations, wire formats, managing the stateful bits, etc

emccue16:06:59

but at least those 2 things are common

Jakub Šťastný16:06:09

So so far I heard a lot about honeysql. Is anyone using datomic then? I haven't heard it mentioned so far. I know it's commercial (and don't know how much it costs). I've seen on of Rich's talks about it, it looked really neat.

Jakub Šťastný16:06:13

@U3JH98J4R yeah exactly! I used to use Merb when possible, but clients mostly wanted Rails. The effort Yehuda and Carl put into Rails definitely made it MUCH better.

Jakub Šťastný16:06:35

This is really ancient history now :) Time flies.

❤️ 3
paulocuneo17:06:46

don't mean to "de-rail" the conversation but this may be of interest https://12factor.net/

👍 3
vemv17:06:56

Datomic is nice (as I hear that is Crux). Definitely used. It's one of the things that can make a big difference (as opposed to a superficial one) of course, as I argued earlier. Probably honeysql and by extension all SQL choices are used because of a risk/cost assessment - they're familiar, and readily available in our cloud providers. SQL DBs are not functional are you'll have noticed from the Datomic rationale

seancorfield17:06:06

@U024VBA4FD5 When I got into Clojure (eleven years ago, now), pretty much no one was doing JDBC stuff and clojure.contrib.sql had been effectively abandoned. I came to Clojure from a fairly "ordinary" web app background so I needed that -- and that's how I became the maintainer of it starting in 2011 (and it moved to clojure.java.jdbc) and now I maintain next.jdbc as the next generation of c.j.j.

seancorfield17:06:22

Since those early days, I've seen a lot more Clojure folks using JDBC stuff (sometimes with databases I've never heard of). MongoDB seemed pretty popular back then -- I ended up maintaining CongoMongo for several years but Monger was always better maintained and better documented -- but there's been a bit of a backlash against MongoDB (we migrated off it, back to MySQL). I think Datomic and Crux and Asami etc are all very interesting and if I was building a new system from scratch I'd probably start off with Crux at this point I think.

emccue17:06:14

its the weekend and i'm on an unreasonable amount of caffiene, so I'd be willing to help make a choose your own adventure guide for this if one doesn't exist

Jakub Šťastný17:06:51

@U04V70XH6 that's cool! I'm make sure to check these out. Haven't heard of Crux yet.

seancorfield17:06:02

Full disclosure: I'm also the maintainer of HoneySQL these days -- again, because we use it heavily at work. We have 113k lines of Clojure in a monorepo with over 40 subprojects, building over a dozen artifacts (apps and background processes), serving about 40 online dating sites. Our "stack" is mostly: Ring, Component (for lifecycle/dependency management), Compojure (for routing), Selmer (for HTML templating), next.jdbc and HoneySQL (for persistence), and a myriad other libraries "as needed". And this runs on the embedded Jetty server (we used http-kit but New Relic support for it was poor, whereas Jetty is well-supported). We use MySQL heavily. We currently front this with Apache but we're about to switch to Nginx.

Jakub Šťastný17:06:22

@U3JH98J4R what do you refer to by adventure guide please :) ?

Jakub Šťastný17:06:50

@U04V70XH6 that's pretty big. Thanks for sharing that stack, it's good for me to get some idea how things are really run in prod.

seancorfield17:06:54

If we were starting fresh, we'd probably look at Crux (or Datomic). We'd probably look at reitit for routing, maybe compojure-api if we wanted Swagger support for our REST API.

Jakub Šťastný17:06:34

Is leiningen what's being used to generate/manage a project these days?

emccue17:06:41

I think the state of clojure survey had people split ~50/50 between lein and deps.edn

Jakub Šťastný17:06:14

Ah, wasn't aware of deps.edn. Good I asked.

seancorfield17:06:45

We're started with Leiningen back in 2010 because that's all there was. We switched to Boot in 2015 and then to the Clojure CLI/`deps.edn` in 2018. We're very happy with that switch.

seancorfield17:06:00

If you want to try the CLI, check out clj-new for creating new projects so that you'll have test running and JAR-building all setup out of the box (disclosure: I maintain clj-new and depstar for CLI stuff).

emccue17:06:40

@U024VBA4FD5 imagine the menu on the chipotle app

seancorfield17:06:54

@U024VBA4FD5 Are you on macOS/Linux/Windows?

emccue17:06:16

You pick a dish and then your meat, veggie, beans, and rice

seancorfield17:06:41

(luckily, Clojure tastes better than Chipotle! 🙂 🌯 )

emccue17:06:44

so one stack could be jetty, reitit, ring, honeysql, postgres

emccue17:06:51

one order at chipotle can be steak, peppers, pinto beans, brown rice

Jakub Šťastný17:06:58

@U04V70XH6 OK, that sounds good then, I'll go with Clojure CLI then. I did play around with Leiningen a bit and it was OK, but would rather try something else. I'm on Ubuntu. Well, my VPS where I do my coding is, it's the free AWS EC2 instance; I'm on iPadOS.

emccue18:06:15

thats what i mean by adventure guide

emccue18:06:04

just instead of showing how many calories are in roast chicken on each option there is a pro/con list and an example usage

emccue18:06:19

then maybe a web framework would be like a skyrim modlist

emccue18:06:45

or a crunchwrap supreme

Jakub Šťastný18:06:40

@U3JH98J4R ah OK. What adventure guide would you recommend me then? I look for something very simple, I don't like too much magic going on. Rather simple things that are easy to debug, even though it might be a more typing. The app I'm going to write is administration for a real-estate management company, so if you have a flat and don't want to be bothered, they manage it for you. It's a small project for my friend who owns that company, they are just starting and with covid they're not doing terribly great, so I'm doing it as a favour to them with the motive on my side of learning Clojure well enough to be able to apply for serious jobs afterwards.

emccue18:06:49

there are infinite possible combos, but we can recommend combos that taste good without being restricting your ability to choose mongodb for dietary reasons

emccue18:06:03

> I'd be willing to help make a choose your own adventure guide for this if one doesn't exist

emccue18:06:07

no clue if one exists

Jakub Šťastný18:06:09

(As a total off-topic, no idea what kind of food they make in Chipotle, but I live in Mexico, so I get to enjoy a lot of great food over here.)

Jakub Šťastný18:06:46

One thing I'd definitely like some recommendation on is a test framework, since I'll be testing way earlier before I even get to the stage of choosing a DB. Do people use clojure.test? (I understand it's actually shipped with Clojure, isn't it?) I'm generally fond of the BDD style more, I found it more readable, but anything good will do.

emccue18:06:06

i guess first question is how are you going to do your frontend

emccue18:06:12

or how are you thinking of doing it

seancorfield18:06:42

Yes, clojure.test is a good place to start -- it is well-integrated into most Clojure editor/REPL setups.

Jakub Šťastný18:06:21

By some front-end tech. Clojurescript/React/Reagent or Elm, I'm not decided yet. I want to write the server side first, especially as the UI requirements are unclear at the moment, while the data structures are much less subject to change even with the little info I have at this point.

seancorfield18:06:26

If you like BDD style, you may like https://github.com/clojure-expectations/clojure-test which is completely compatible with clojure.test and all clojure.test tooling.

emccue18:06:13

If you are going to write in Elm i think you and I might be the only ones who have gone that blessed path

emccue18:06:34

(at least I don't hear much about the combo)

emccue18:06:44

but anyways - if you are using clojurescript then edn and transit are options for a wire format, they might be more convenient depending on how you want to handle things. I still havent finished or tested my elm transit library so you are probably "stuck" with JSON as your safest bet

seancorfield18:06:53

(I learned Elm some years ago but never used it for anything "real" -- I built a dashboard app for my solar panel setup -- I think it is very interesting)

Jakub Šťastný18:06:18

@U3JH98J4R oh really? That's interesting! How is your experience? I'm as new to Elm as I'm to Clojure. I like the promise of no runtime errors, I hate brittle front-ends. (I've been debugging IE 6 issues back in the day, so anything that can prevent things breaking on the frontend.)

emccue18:06:27

Same as seancorfield I haven't managed to use elm for production, but I like the language a lot.

Jakub Šťastný18:06:30

@U04V70XH6 it's very cool. In some ways too opinionated, but if it lives to the hype (which I don't know yet, I only played with it a little bit), then worth it.

emccue18:06:34

I think the tradeoffs of having a strict static type system are well worth it on the frontend

Jakub Šťastný18:06:54

Yeah, 100% agreed.

emccue18:06:13

so after the data format piece, I'd recommend using Jetty for your server

seancorfield18:06:21

I would probably default to ClojureScript for a frontend these days. Start with re-frame and figwheel-main and see how I got on.

Jakub Šťastný18:06:28

@U3JH98J4R yeah for format, I'll go with JSON. I do prefer EDN most definitely, but JSON is more supported.

emccue18:06:34

so ring-jetty and then reitit for routing.

Jakub Šťastný18:06:03

@U04V70XH6 any particular reason for CLJS over Elm? I'm not decided on either. Leaning more towards Elm probably, but the jury is still out.

emccue18:06:31

I haven't ever learned crux or datomic which is a flaw in myself, but if you also would go for sql for primacy reasons I would use Postgres with next.jdbc

seancorfield18:06:37

The ability to share logic/code between the front and back ends is very appealing. For example, HoneySQL is all .cljc files but most folks would expect it to be used just on the server to format SQL ... but someone built this: https://www.john-shaffer.com/honeysql/ where HoneySQL is run in the client.

Jakub Šťastný18:06:39

Checking jetty now.

emccue18:06:52

and then you would use a connection pooling library, which I would use HikariCP for

Jakub Šťastný18:06:21

@U04V70XH6 that's a very good point actually.

seancorfield18:06:05

Re: HikariCP -- next.jdbc has built-in support for leveraging HikariCP to create pooled connections BTW.

Jakub Šťastný18:06:41

Do I need Hikari in the beginning? The app is not going to be used by many people.

Jakub Šťastný18:06:43

Looks well made, good documentation.

seancorfield18:06:51

It's just a dependency to add to deps.edn and then use next.jdbc's ->pool function or component (if you go with Stuart Sierra's Component library for lifecycle management/dependency injection).

emccue18:06:32

This is what part of my dependencies look like for a small project i wrote

seancorfield18:06:57

See https://github.com/seancorfield/usermanager-example for an example (which also has a Polylith version, since someone mentioned that).

Jakub Šťastný18:06:57

Great! That's really helpful!

seancorfield18:06:25

There's a reitit / Integrant variant of that app linked from the README too.

emccue18:06:34

and I used flyway for managing migrations

emccue18:06:50

so this is what the section of deps I needed for running unit tests looked like

emccue18:06:51

with some somewhat hacky code for spinning up an embedded postgres in tests

emccue18:06:49

This code doesn't power a product or anything so i'm perfectly willing to share if you want a starting point

Jakub Šťastný18:06:30

@U3JH98J4R well if you don't mind, it'd be great. Loved the one Sean posted, but having another reference would be great.

emccue18:06:01

can you share a github username?

seancorfield18:06:45

If you get stuck on any of the “nuts’n’bolts” level stuff with Clojure, you’ll find the #beginners channel here is extremely friendly and folks have opted in to helping/teaching folks who are learning. Your question here — about approach and mindset — was appropriate for #clojure so I’m just offering that as a pointer for when you get into the implementation stuff and need assistance. Folks in the #clojure channel tend to assume a base level of experience so sometimes you might get a rather “short” answer 🙂 whereas you’ll get a lot more hand-holding in #beginners

Jakub Šťastný18:06:05

Thanks @U04V70XH6, I'm definitely going to hang out there a lot!

seancorfield18:06:04

And if you end up using any of my libraries or tools and have questions, feel free to DM me (I might direct you to a channel if one exists that is related to a given library/tool but I usually try to answer DMs in addition).

Jakub Šťastný20:06:57

Thanks @U04V70XH6, I'll keep that in mind.

noisesmith16:06:18

going way back up thread, @U01KQ9EGU79 - when we use httpkit vs. jetty etc. that is a single point of integration (which ring plugin is used, and that comes down to what the line of code that starts the server looks like) - in nearly every case that is going to be an input to ring, and everything else is coded in terms of ring (or more rarely you use pedestal, but there too, only a single line of code or so is determined by the server background and all other code is written in terms of the server abstraction)

noisesmith16:06:58

in fact it's relatively common to use different server implementations in dev vs. prod, and simply swap them out via config

West15:06:57
replied to a thread:Hey guys, how do people in the Clojure world _think_ about writing web apps? Example: In Ruby people think "what framework is good for my use-case?" first. Many of them go with Rails, which does everything for them (and gets into the way in my opinion, but whatever, this is how the mentality goes). Everybody would use some heavy ORM. If a new service is needed, for instance some queue, it'd always run in a separate process (MRI Ruby has GIL and mutable state, no one uses threading). So then the app would run in an application server and say Nginx would proxy to it. How about Clojure? I'm asking to _get the right mindset_ before I start searching for concrete libraries and tools. Being used to certain ways, it's easy to try to do things the old way rather than ponder what approach suits to this technology. So for instance I might be used to large web frameworks and I start searching for "best web framework clojure", while using a framework might not even be a thing in the Clojure world and maybe library approach is more popular instead? So this is what I would like your help with, to *suggest me a mindset I should adopt* towards this. I'm suspecting Clojure is not going to take the framework approach as much and likely will use few *lighter libraries* instead. In that case what are the libraries and how do they work together? I mentioned *the process/threading issue*, that's another big question I have. Once I was playing with JRuby and it seemed to me that it tries to put everything into the same process and use threading and avoid starting new processes at all cost. Does Clojure take this approach as well? The app I'm going to write is just REST API, no views will be generated on the server.

Maybe a noob question. It seems like a lot of people really like to write clojure webapps by using a server library. Whether that’s with httpkit, jetty, ring, or whatever, you’re hand-rolling a server. What’s the advantage over using something like nginx, apache, or even caddy?