clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
kennytilton 2021-06-19T05:46:38.102Z

That's it! Thx. 🙏

zendevil 2021-06-19T06:35:35.106200Z

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

2021-06-19T08:15:42.114100Z

(get-in v [i j])

💯 1
quoll 2021-06-19T11:13:28.121500Z

Much more concise, but slower

2021-06-19T12:14:32.133400Z

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

2021-06-19T12:17:03.133600Z

still slower, but not an order of magnitude )

vemv 2021-06-19T12:17:36.133800Z

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

alexmiller 2021-06-19T13:47:42.153200Z

I think we actually have an enhancement ticket for this

alexmiller 2021-06-19T13:50:01.154Z

https://ask.clojure.org/index.php/729/aset-aget-perform-poorly-multi-dimensional-arrays-even-hints?show=729#q729 is the one I’m thinking of, votes welcome

zendevil 2021-06-20T06:18:39.231700Z

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?

2021-06-20T06:45:50.232100Z

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

zendevil 2021-06-20T09:00:35.233100Z

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 2021-06-20T09:47:57.233600Z

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
@quoll upon checking, get-in performs better than recursive nth’s.

💯 1
2021-06-20T09:53:30.233900Z

(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

2021-06-20T09:54:40.234100Z

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

zendevil 2021-06-20T10:07:15.234300Z

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

2021-06-20T10:09:57.234500Z

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

2021-06-20T10:10:49.234700Z

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

zendevil 2021-06-20T10:11:27.234900Z

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

zendevil 2021-06-20T10:16:13.235100Z

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"

2021-06-20T10:17:19.235300Z

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

zendevil 2021-06-20T10:17:30.235500Z

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

2021-06-20T10:19:28.235800Z

what precisely surprising you? )

zendevil 2021-06-20T10:23:35.236Z

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.

2021-06-20T10:29:27.236200Z

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

2021-06-20T10:30:13.236400Z

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

zendevil 2021-06-20T10:30:33.236600Z

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

2021-06-20T10:32:36.236900Z

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

zendevil 2021-06-20T10:37:37.237100Z

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 2021-06-20T10:39:09.237500Z

@delaguardo backwards compatibility is a factor I suppose.

zendevil 2021-06-20T10:58:18.237800Z

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

2021-06-20T11:14:13.238200Z

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

2021-06-20T11:15:19.238400Z

Plus I'm not sure this is backward compatible change

chrisn 2021-06-20T16:08:27.244500Z

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 2021-06-19T06:40:16.107200Z

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

indy 2021-06-19T07:15:36.113700Z

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

indy 2021-06-19T08:31:14.114700Z

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

quoll 2021-06-19T11:30:50.133100Z

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

1
zendevil 2021-06-19T06:42:30.108400Z

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

2021-06-19T09:27:31.120500Z

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

jaihindhreddy 2021-06-19T10:03:57.120700Z

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.

👎 1
joshkh 2021-06-19T06:44:49.108700Z

just spitballing here:

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

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

joshkh 2021-06-19T06:46:16.109200Z

ah, that applied before your edit 🙂

joshkh 2021-06-19T06:54:29.112300Z

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

joshkh 2021-06-19T07:03:38.113300Z

thanks @nenadalm. 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?

joshkh 2021-06-19T07:05:42.113500Z

actually, scratch that question 😉

joshkh 2021-06-19T06:56:10.113Z

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

nenadalm 2021-06-19T06:58:37.113100Z

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

eskos 2021-06-19T08:58:06.117400Z

@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

joshkh 2021-06-19T09:24:23.118900Z

I think the question was from someone else :)

zendevil 2021-06-19T09:24:51.119800Z

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-himik 2021-06-19T09:27:25.120300Z

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

2021-06-19T13:10:44.149100Z

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.

2021-06-24T16:19:18.397900Z

going way back up thread, @c.westrom - 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)

2021-06-24T16:19:58.398100Z

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

p-himik 2021-06-19T13:21:25.149200Z

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

2021-06-19T13:40:47.152600Z

Thanks @p-himik! 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-himik 2021-06-19T13:50:26.154300Z

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.

javahippie 2021-06-19T13:53:18.154500Z

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.

2021-06-19T14:21:41.156200Z

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

2021-06-19T14:22:04.156800Z

Will check Luminus out, cheers @javahippie!

vemv 2021-06-19T16:00:42.157900Z

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.

vemv 2021-06-19T16:05:19.158200Z

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

2021-06-19T16:07:01.159600Z

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

2021-06-19T16:07:15.160Z

@vemv interesting.

p-himik 2021-06-19T16:07:18.160200Z

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

West 2021-06-19T16:09:43.160500Z

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

vemv 2021-06-19T16:12:09.160700Z

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-himik 2021-06-19T16:14:29.160900Z

@c.westrom 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-himik 2021-06-19T16:27:18.161200Z

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

2021-06-19T16:33:17.167200Z

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

2021-06-19T16:33:57.167800Z

Anyway better don't get me started on Rails 🙂

emccue 2021-06-19T16:36:11.168Z

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

emccue 2021-06-19T16:36:50.168200Z

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

emccue 2021-06-19T16:37:11.168400Z

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

emccue 2021-06-19T16:38:31.168700Z

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

2021-06-19T16:38:55.169500Z

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

emccue 2021-06-19T16:39:01.169700Z

yeah kinda

emccue 2021-06-19T16:39:42.169900Z

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)

emccue 2021-06-19T16:40:14.170100Z

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

vemv 2021-06-19T16:41:13.170300Z

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

emccue 2021-06-19T16:42:32.170500Z

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

emccue 2021-06-19T16:42:59.170800Z

but at least those 2 things are common

2021-06-19T16:44:09.172600Z

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.

2021-06-19T16:45:13.174Z

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

2021-06-19T16:45:35.174500Z

This is really ancient history now :) Time flies.

❤️ 1
paulocuneo 2021-06-19T17:03:46.174800Z

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

👍 1
vemv 2021-06-19T17:04:56.175100Z

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

seancorfield 2021-06-19T17:09:06.175400Z

@jakub.stastny.pt_serv 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.

seancorfield 2021-06-19T17:13:22.175600Z

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.

emccue 2021-06-19T17:16:14.175900Z

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

2021-06-19T17:17:51.177Z

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

seancorfield 2021-06-19T17:18:02.177400Z

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.

2021-06-19T17:18:22.177800Z

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

2021-06-19T17:19:50.178900Z

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

seancorfield 2021-06-19T17:19:54.179100Z

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.

2021-06-19T17:42:34.180Z

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

emccue 2021-06-19T17:46:41.180200Z

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

2021-06-19T17:47:14.180600Z

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

seancorfield 2021-06-19T17:55:45.180800Z

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.

seancorfield 2021-06-19T17:57:00.181Z

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

emccue 2021-06-19T17:57:40.181200Z

@jakub.stastny.pt_serv imagine the menu on the chipotle app

seancorfield 2021-06-19T17:57:54.181400Z

@jakub.stastny.pt_serv Are you on macOS/Linux/Windows?

emccue 2021-06-19T17:58:16.181900Z

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

seancorfield 2021-06-19T17:58:41.182600Z

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

emccue 2021-06-19T17:58:44.182900Z

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

emccue 2021-06-19T17:59:51.184400Z

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

2021-06-19T17:59:58.184800Z

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

emccue 2021-06-19T18:00:15.185Z

thats what i mean by adventure guide

emccue 2021-06-19T18:01:04.185600Z

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

emccue 2021-06-19T18:02:19.187300Z

then maybe a web framework would be like a skyrim modlist

emccue 2021-06-19T18:02:45.188100Z

or a crunchwrap supreme

2021-06-19T18:03:40.190Z

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

emccue 2021-06-19T18:03:49.190200Z

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

emccue 2021-06-19T18:04:03.190400Z

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

emccue 2021-06-19T18:04:07.190600Z

no clue if one exists

2021-06-19T18:07:09.192300Z

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

2021-06-19T18:10:46.194500Z

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.

emccue 2021-06-19T18:11:06.194700Z

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

emccue 2021-06-19T18:11:12.194900Z

or how are you thinking of doing it

seancorfield 2021-06-19T18:11:42.195900Z

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

2021-06-19T18:12:21.197100Z

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.

seancorfield 2021-06-19T18:12:26.197300Z

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.

emccue 2021-06-19T18:14:13.197600Z

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

emccue 2021-06-19T18:14:34.197800Z

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

emccue 2021-06-19T18:15:44.199100Z

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

seancorfield 2021-06-19T18:15:53.199600Z

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

2021-06-19T18:16:18.200400Z

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

emccue 2021-06-19T18:17:27.201700Z

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

2021-06-19T18:17:30.202Z

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

emccue 2021-06-19T18:18:34.202200Z

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

2021-06-19T18:18:54.202600Z

Yeah, 100% agreed.

emccue 2021-06-19T18:19:13.202800Z

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

seancorfield 2021-06-19T18:19:21.203Z

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

2021-06-19T18:20:28.204100Z

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

emccue 2021-06-19T18:20:34.204400Z

so ring-jetty and then reitit for routing.

2021-06-19T18:21:03.205400Z

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

emccue 2021-06-19T18:21:31.205600Z

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

seancorfield 2021-06-19T18:21:37.205900Z

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.

2021-06-19T18:21:39.206100Z

Checking jetty now.

emccue 2021-06-19T18:21:52.206300Z

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

2021-06-19T18:22:21.206800Z

@seancorfield that's a very good point actually.

seancorfield 2021-06-19T18:23:05.207Z

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

2021-06-19T18:23:41.207800Z

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

2021-06-19T18:24:43.208400Z

Looks well made, good documentation.

seancorfield 2021-06-19T18:24:51.208600Z

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

2021-06-19T18:25:19.208900Z

Fair enough.

emccue 2021-06-19T18:25:32.209100Z

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

seancorfield 2021-06-19T18:25:57.209500Z

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

2021-06-19T18:25:57.209700Z

Great! That's really helpful!

seancorfield 2021-06-19T18:26:25.210100Z

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

emccue 2021-06-19T18:26:34.210300Z

and I used flyway for managing migrations

emccue 2021-06-19T18:26:38.210500Z

emccue 2021-06-19T18:26:50.210700Z

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

emccue 2021-06-19T18:27:51.210900Z

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

emccue 2021-06-19T18:27:54.211200Z

emccue 2021-06-19T18:29:49.212100Z

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

2021-06-19T18:30:30.213300Z

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

emccue 2021-06-19T18:31:01.213500Z

can you share a github username?

2021-06-19T18:32:29.213800Z

jakub-stastny

seancorfield 2021-06-19T18:34:45.214Z

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

2021-06-19T18:38:05.214600Z

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

seancorfield 2021-06-19T18:44:04.214800Z

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

2021-06-19T20:16:57.215300Z

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