This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-06-10
Channels
- # announcements (1)
- # babashka (178)
- # beginners (216)
- # bootstrapped-cljs (1)
- # brompton (5)
- # calva (3)
- # chlorine-clover (1)
- # clj-kondo (2)
- # cljdoc (37)
- # cljfx (4)
- # cljs-dev (2)
- # clojure (360)
- # clojure-chile (8)
- # clojure-europe (3)
- # clojure-italy (5)
- # clojure-nl (9)
- # clojure-spec (2)
- # clojure-sweden (1)
- # clojure-uk (61)
- # clojuredesign-podcast (1)
- # clojurescript (83)
- # clr (2)
- # conjure (4)
- # core-async (14)
- # cursive (20)
- # data-science (2)
- # datomic (15)
- # docker (11)
- # emotion-cljs (1)
- # figwheel-main (28)
- # find-my-lib (1)
- # fulcro (46)
- # helix (16)
- # honeysql (14)
- # jobs (10)
- # jobs-discuss (17)
- # joker (1)
- # juxt (9)
- # kaocha (8)
- # leiningen (3)
- # meander (3)
- # news-and-articles (1)
- # off-topic (110)
- # pathom (7)
- # pedestal (4)
- # protojure (2)
- # re-frame (12)
- # reagent (25)
- # ring (4)
- # shadow-cljs (109)
- # spacemacs (9)
- # specter (1)
- # sql (3)
- # tools-deps (23)
Hi all, I am making a simple application and having troubles with how to add middleware such that my request body is converted from JSON to Clojure map, this is sample code for the same
(ns dice_api.handler
(:require [compojure.api.sweet :refer :all]
[ring.middleware.json :refer [wrap-json-response wrap-json-body]]
[dice_api.schema :refer [roll_value]]
[dice_api.dice :refer [roll_dice]]
[schema.core :as s]
[ring.util.response :refer [response]]))
(defn post-handler
""
[req]
(let [n (get-in req [:body :n])
s (get-in req [:body :s])]
(response (roll_dice n s))))
(def api-routes
(api
{:swagger
{:ui "/"
:spec "/swagger.json"
:data {:info {:title "Dice-api"
:description "Compojure Api example"}
:tags [{:name "api", :description "some apis"}]}}}
(context "/api" []
:tags ["api"]
(GET "/roll/:n/:s" []
:path-params [n :- s/Int
s :- s/Int]
:return roll_value
:summary "Rolls :n dice of sides :s"
(response (roll_dice n s)))
(POST "/roll" request
:summary "roll with body params"
:return roll_value
(post-handler request)))))
(def app
(-> api-routes
(wrap-json-body {:keywords? true})
wrap-json-response))
This works if I change api
to routes
, but swagger starts failing after that
Basically the body of my main application is not going to have a fixed structure other than some key terms, so how to handle that with the swagger support?
@puru072 Because you're using :refer :all
everywhere it's pretty much impossible for us to tell what names are coming from which namespaces -- I'd recommend using :as
with aliases to make your code easier for others to read. And your parenthesis layout is very distracting too -- definitely not idiomatic to have ))
on separate lines. https://guide.clojure.style/ is a good read for recommendations on laying out your code.
I have updated the code, removed unnecessary stuff, removed :all
and fixed the paranthesis
I looked at compojure.api.sweet
it looks like the api
function returns a record (like a hash map) so I'm a bit puzzled about how that's supposed to work (I've never used compojure-api
).
You'll have to find someone who's familiar with that library. The maintainers are in Europe I think so when they come online they might be able to help you (right now it's 4:30 am for them I think).
okay, thanks for the response, will wait for them
(s/def ::a (s/keys :req [::b] :opt [::c])) if I want to have or :opt [::d] or :opt [::e]
how I proceed?
with (s/or ?
I'm having this error
Assert failed: spec/or expects k1 p1 k2 p2..., where ks are keywords (c/and (even? (count key-pred-forms)) (every? keyword? keys))
didn't understood =(
@d.ian.b You need to share the actual code that produces that error, otherwise we can only guess what you're doing...
er, proprietary ^^'
Then create a non-proprietary repro that you can share.
it's reaaaally difficult to anonimize this case
I was trying something like
What I suspect you're doing is putting something inside the :opt
vector -- and the error says (every? keyword? keys)
... so you have non-keyword things in there
(s/def ::a (s/or (s/keys ,,, (s/keys ,,, (s/keys ,,,
but not working
Also, use triple backticks around your code so it's easier for us to read 🙂
OK, s/or
requires a label for each variant. See the examples on http://clojure.org
I thought that it's reaally simple the example
to not have triplebacktick
thanks, I'll see
You can also use single backticks
for inline code.
most part of the day I use prismatic/schema
thanks
I have updated the code, removed unnecessary stuff, removed :all
and fixed the paranthesis
Is there any value in using lazy-seq in the following:
(defn x [b c]
(when (< b c)
(cons b (lazy-seq (x (inc b) c)))))
With or without, it results in a Cons type that seems to be realized when the function executes?@alwyn You can see the difference here:
user=> (defn x [b c]
(println 'x b c)
(when (< b c)
(cons b (lazy-seq (x (inc b) c)))))
#'user/x
user=> (x 0 5)
x 0 5
(x 1 5
0 x 2 5
1 x 3 5
2 x 4 5
3 x 5 5
4)
user=> (defn x [b c]
(println 'x b c)
(when (< b c)
(cons b (x (inc b) c))))
#'user/x
user=> (x 0 5)
x 0 5
x 1 5
x 2 5
x 3 5
x 4 5
x 5 5
(0 1 2 3 4)
user=>
Does the difference make sense to you?
@seancorfield that or stuff was a way too much complex for the stuff what I thinking that I had to do 😃
I solved in the before specs 😃
@seancorfield looking at your output it makes sense to me. Weird thing is when I execute with println in my repl and cider the lazy-seq output looks like the non-lazy output.
user=> (defn x [b c]
#_=> (println 'x b c)
#_=> (when (< b c)
#_=> (cons b (lazy-seq (x (inc b) c)))))
#'user/x
user=> (x -2 2)
x -2 2
x -1 2
x 0 2
x 1 2
x 2 2
(-2 -1 0 1)
user=> (x 0 5)
x 0 5
x 1 5
x 2 5
x 3 5
x 4 5
x 5 5
(0 1 2 3 4)
user=>
You can "blame" CIDER for that, or rather nREPL I suspect -- it separates results and output.
In the standard REPL, you can see the difference because output and results are interleaved.
(even unrepl does this I think -- and prepl is designed to do it too)
When I use leiningen to run the repl it uses nrepl with the same results, how do you start yours?
I haven't used Leiningen for years. I switched to Boot in 2015 and then to the Clojure CLI / deps.edn
in 2018.
You should be able to see the difference in behavior, even in nREPL/CIDER, if you take 3
on the call:
;; non-lazy version:
user=> (take 3 (x 0 5))
x 0 5
x 1 5
x 2 5
x 3 5
x 4 5
x 5 5
(0 1 2)
;; lazy-seq version:
user=> (take 3 (x 0 5))
x 0 5
(x 1 5
0 x 2 5
1 2)
user=>
Interesting to see that even in a plain lein repl
you can't see the difference (I just tried it). At least you can see the difference in lein repl
with take 3
on it.
(it's still not interleaved, but at least you see six prints with the non-lazy version and just three with the lazy version)
So in a typical test from 4clojure like (= (x 1 3) '(1 2)) When using the lazy-seq, is it the = that causes the lazy-seq to be realized?
Yes, because it has to realize enough elements to decide whether they are equal or unequal.
;; lazy version
user=> (= (x 1 3) '(1 2))
x 1 3
x 2 3
x 3 3
true
user=> (= (x 2 3) '(1 2))
x 2 3
false
user=>
Three calls are needed in the first example because it has to establish that it gets 1, then 2, then the end of the sequence.
Only one call is needed in the second example because it returns (2 ...)
which is not equal to (1 ...)
in the first element.
my terminology might not be idiomatic, but does that mean that comparison is in a sense overloaded to support the sequence abstraction?
https://clojure.org/guides/equality is probably the best thing to read about that.
^ Henry Baker's "Equal Rights for Functional Objects" is a good paper to check out in that area.
@ikitommi I get that api
already has JSON middleware, but the post api that I want to make is not supposed to have pre-defined body structure other than some fixed key terms, and this piece of code is giving me an exception when I try to make a post request, is there a way to handle this case without specific body structure
(ns dice_api.handler
(:require [compojure.api.sweet :refer :all]
[ring.middleware.json :refer [wrap-json-response wrap-json-body]]
[dice_api.schema :refer [roll_value]]
[dice_api.dice :refer [roll_dice]]
[schema.core :as s]
[ring.util.response :refer [response]]))
(defn post-handler
""
[req]
(let [n (get-in req [:body :n])
s (get-in req [:body :s])]
(response (roll_dice n s))))
(def api-routes
(api
{:swagger
{:ui "/"
:spec "/swagger.json"
:data {:info {:title "Dice-api"
:description "Compojure Api example"}
:tags [{:name "api", :description "some apis"}]}}}
(context "/api" []
:tags ["api"]
(GET "/roll/:n/:s" []
:path-params [n :- s/Int
s :- s/Int]
:return roll_value
:summary "Rolls :n dice of sides :s"
(response (roll_dice n s)))
(POST "/roll" request
:summary "roll with body params"
:return roll_value
(post-handler request)))))
(def app
(-> api-routes
(wrap-json-body {:keywords? true})
wrap-json-response))
@ikitommi Is the result of api
(which seems to be a Route
record) even compatible with regular Ring middleware? I wasn't able to determine that from the docs (or from my REPL experiments).
Ah... OK. Definitely not clear from the docs. Any idea why @puru072’s example doesn't work?
or cemerick/url
I'd just use the Java directly too though
Ok! I'm having a tough one with this function. I'm trying to transform a flat list (of items that represent an entry in a table of contents) into nested hiccup. Each item in the list has a :level
, so if the level of an item is more than the previous item it needs be nested into the previous. I played with recursion last night but I couldn't figure out a solution with loop/recur, and using function-name recursion ... it almost worked, but I also read it can "blow the stack"? I'm hoping there is an idiomatic function that exists to solve this that already that I'm missing, but I'm not sure. Anyone care to take a look at this gist?
https://gist.github.com/teesloane/0030eb118d80504c955ad71be16bf791
Would using group-by on :level make it easier? (Assuming one can add something to indicate the order in the specific level?
I don't think group-by on :level would make it easier for this use case, since the order of all items is significant, and should be preserved in the return value. group-by would "forget" the relative ordering of items with different :level values
partition-by
maybe?
I am thinking that perhaps using reduce
in some way to produce a 'generic tree structure' from the input, where a new element [level other-stuff] is added as a new right sibling to the last node that was seen with the same value of level
, if there was any, and if not, I am guessing the new level
value must be one greater than the previous value, and becomes a child of the previous node. One could implement a sanity error check that every value of level
was either one more than the previous one, or less.
Maybe, reduce and check the last element of the accumulator and depending on the relation between levels, push it as a child or as a sibling. Edit: I think that's what you were saying @U0CMVHBL2 - I am still waking up. Thanks for the input.
Yes, at that level of detail, that is what I was thinking. Code is more specific than English, of course, but sounds like you have the same basic idea.
If you always have a correct tree structure for the N-1 elements seen so far, and if you have an efficient way to "walk the rightmost branches" down "level" steps deep and add a new leaf there, then that is what each single reduce step could do.
From the gist I posted - is the example of using recursion "by function name" (for a lack of better words?) ... not idioimatic / or potentially problematic? I didn't quite get the solution, but I got close. I'm not super familiar with recursion capabilities of clojure, but I recall folks saying that certains means of doing it are supported while other's aren't. (clj-kondo flagged one of my usages as not correct).
If you know that your recursion depth is limited to a reasonably small amount, e.g. a few hundred deep or so, then I wouldn't worry about it. If your recursion depth is one per element of a sequence/list/vector, and you want your code to be able to handle sizes of those things that are tens of thousands of elements long, then you will likely exceed default JVM stack depth limits.
loop/recur does not have that limit
Does anybody know about a lazy text file slurper? When dealing with huge files, it would be really handy to be able to use to pass something like (fn [n] (slurp "path/to/file.csv" :only-line n) into a process and only load the lines that are needed into memory.
Does some combination of line-seq
and drop
do what you need?
I'll take a look at that, thanks!
I read about those, and it looks like line-seq and drop-last would do what I want. I am slightly concerned about access time though. Seems like it would take O(N) time to reach the Nth line in a file, wouldn't it? That's fine if you're accessing the lines in order, but if you need to access the lines out of order for some reason that could get really slow.
In general, the beginning of line N in a file could be anywhere from N bytes from the beginning of the file, to any arbitrary position later.
Unless someone has a pre-built index of the starting position of some of the lines of the file, I do not see how you can avoid reading the entire file up to the point you want.
Or if you somehow know that all lines have a particular length in bytes.
Oh I see. You need to parse the whole thing so until you've counted the correct number of \n's.
Gotcha.
Right, unless you know more about the structure of the file than simply 'it is a text file'
If you have control over the way the columnated data is generated, you might be willing to assume that all lines have X bytes, but other than that you have to assume the line lengths could be anything.
Sounds good.
There's a reason why people prefer databases for large data 🙂
If you want 'random' access to lines of a text file, and your file system gives you a quick way to jump to byte offset X within a text file, as I think most file systems do, then building and maintaining an index like "line 100 begins at byte offset 39104, line 200 begins at byte offset 77025, etc." would let you drastically reduce the number of bytes that you would need to scan to find the start of the line you are interested in.
You can trade off the number of lines you keep that info about in the index, versus the worst case scan time needed to find the line you want.
Java has support for every version of this you might want, either via streaming Readers, RandomAccessFile, or memory mapped files with MemoryMappedBuffer
Clojure does not really wrap any of that stuff but it's trivial to use via interop
Yes, people prefer databases for large data
RandomAccessFile sounds like the thing I would want
Andy & Alex - thank you for taking the time to answer! I appreciate it 🙂
I read about those, and it looks like line-seq and drop-last would do what I want. I am slightly concerned about access time though. Seems like it would take O(N) time to reach the Nth line in a file, wouldn't it? That's fine if you're accessing the lines in order, but if you need to access the lines out of order for some reason that could get really slow.
Is there a way to cancel a future that I no longer have a reference to? I started a background task to log some info to console. I want to shut it up.
no, unless you want to completely (shutdown-agents)
(but that will prevent all futures from working until you restart the jvm)
6 years ago I switched to Clojure as my primary technology stack, mostly because of Datalog. If you are a beginner and you'd like to learn Datalog (the query language used to interact with Datomic), I recorded a video of me following chapters 0-4 of the excellent interactive Datalog tutorial, Learn Datalog Today: https://youtu.be/8bc4mBRmmbg (Full credit for the syllabus to @jonas and @robert-stuttaford)
Hey you mention in the beginning of the video that there are classes of queries in sql that are impossible or very hard to write. Do you have any example of those? I'm also interested in shortcoming/trade-offs of Datalog? I find it particular helpful to know about the downsides of a technology and always appreciate hearing from experts which challenges they have met, using those.
Hi, I'm trying to wrap my head around core async and transducer. I wrote a function that gets a collection over an input channel and puts only elements that are not known on the output channel
(defn rem [coll in]
(let [out (async/chan)]
(async/go-loop [state coll]
(let [new (filter #(not-in? state %) (async/<! in))]
(async/>! out new)
(recur (set/union state new))))
out))
I just saw the talk "Core.Async in Use - Timothy Baldridge" and I noticed the similarities with his first example. He came up with a solution using transducers which made me think if I could simplify this function in the same way. But to be honest I"m kinda lost here don’t even know how to start 🙈 Any input that get me started is really appreciated 😇just keep in mind that distinct will build up state over time. dedupe
transducer removes dupes that are adjacent (and thus does not have that issue)
I need to analyze the contents of a specific directory. In the past I've put an absolute path into a string and used that. It didn't feel idiomatic. Should I add the target directory to the :paths
entry? https://clojure.org/reference/deps_and_cli#_paths
not clear in what way that would help you
if your code is using this as an argument then I would use typical means of conveying parameters to programs (main args, Java system properties, env vars, config files, etc)
Cool. The existence of io/resource
made me think, I'm supposed to treat paths that are program input in a special way
the reason for io/resource is to allow treating files on disk during development the same as resources inside a jar when deployed, without conditionals in your code
it's meant for the kind of thing that the app carries with itself
(defn manage-parallel-tasks [tasks shared-state-atom]
;; Create a management function to close over
;; the shared state and the future tasks so that we can pass
;; commands and get status or cancel background tasks.
(let [management-task (fn [cmd]
(case cmd
:done (println "Final result: " @shared-state-atom)
:end (run! future-cancel tasks)
:status (println "Num processed: " @shared-state-atom)))]
;; By default, loop until every task is complete.
(future (loop [tasks tasks]
(Thread/sleep 500)
(if (every? future-done? tasks)
(do (println "Done processing.")
(management-task :done))
(do
(println "Still processing...")
(recur tasks)))))
management-task))
;; Example of a long-running subtask.
(defn make-sub-task [shared-state-atom]
(future
(loop [shared-state-atom shared-state-atom]
(if (< (Math/random) 0.9)
(do
(Thread/sleep 500)
(swap! shared-state-atom inc)
(recur shared-state-atom))))))
(let [shared-state-atom (atom 0)
sub-tasks [(make-sub-task shared-state-atom)
(make-sub-task shared-state-atom)]
management-task (manage-parallel-tasks sub-tasks shared-state-atom)]
(Thread/sleep 2000)
(management-task :status)
(Thread/sleep 3000)
(management-task :status)
(management-task :end))
Is this a reasonable way to spawn some long-running subtasks and monitor them? This is a simplified version of the code I'm having trouble with.
The problem I'm seeing in the real code is my REPL becoming unresponsive or background work still running after I presume I cancel all sub-tasks by calling the management task with :end
.
My guesses as to where the issue is:
• Too many sub-tasks all trying to swap an atom too quickly? Thrashing?
• Running out of threads?
• The pattern is reasonable. My problems are in code outside this pattern.
• Unknown unknown? I'm still making progress confirming or disconfirming the above by reading and playing with code. But I also think it's likely I'm doing something way off.future-cancel is not reliable, it's opt in, you need to ensure anything that should be cancellable do one of the things that future-cancel can effect
(sleep or wait on IO, more or less)
otherwise, the task can check if the thread it is in is cancelled and exit gracefully
but there's no reliable way to simply kill it from the outside
Ahhhh. Thanks! That sounds exactly like it would cause the issue. The subtasks I'm running are process-heavy, not sleep/io.
you can add a check of (.isInterrupted (Thread/currentThread))
to the loop / process https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Thread.html#isInterrupted()
future-cancel
will make that return true
Hi folks 👋
How do I go from {[2020 1] 8000, [2020 2] 6000}
to [{:year 2020 :month 1 :amount 8000} {:year 2020 :month 2 :amount 6000}]
in an idiomatic way?
you're going from unordered collection (a map) to an ordered collection (a vector) and it looks like you will want a sort-by :month
.
or not. just wanted to point out that the map you're starting with is not sorted by month even though it printed in that order
How to people typically figure out what fields are available in large config maps (aside for online docs)? For example, the Pedestal service map.
(-> the-map (keys) (sort))
in the REPL is something I tend to do. Or just eval the map into REBL so I have a visual (tabular) representation that is sortable by keys. I use REBL all the time to help understand and navigate data.
So say you come across an interesting repo on github or whatever. Do you clone it and start up a REPL (REBL too?) instance in your editor to start exploring it? Just reading the code on github hasn't worked for me because I can't understand the inputs. (please see below)
When someone's asking about a library on Slack, I'll often run clj -Sdeps '{:deps {the-library {:mvn/version "RELEASE"}}}'
which starts a REPL with just that library as a dependency and then I'll play with it to see if I can answer their question. I might also add -A:rebl-11
to that (an alias from my dot clojure file) so I get REBL started up -- Cognitect's data/code visualizer. I agree that reading code on GitHub isn't always enough -- but with practice it definitely gets easier over time. It can be a hard slog when you're getting started...
I like that strategy. That one liner definitely makes it seem a lot more frictionless in getting my hands dirty quickly with an unfamiliar repo
Can I piggieback of this question to a more general level on understanding function inputs? It's by far my biggest struggle with Clojure.
I just can't form a mental picture of what the function inputs are like I can with a type annotated language. Just today, when looking through various projects I was interested in, I am seeing function parameters like `request` (which I know with web dev now usually means a hashmap), `req` (same), `params` , `rules`, etc.
None of these functions have doc strings which I've found is quite common. It leads me to believe that I just don't have the understanding that other Clojure users do about what these parameters probably look like.
t's honestly caused me to leave Clojure a few times. I always come back because it is by far my favorite language to actually write code in (and this slack community has been my favorite so far, tbh). I just can't read anybody's code (including my own sometimes months later)!
There are some conventions that help here. But because clojure is so liberal with the shorthands and shortcuts it lets you take in writing code, an undisciplined codebase can become unreadable.
I often end up resorting to capturing the data inside the function call, and then investigating it in the same manner @seancorfield describes above
Which I get. I know the common ones like m
for map
but even in the most popular Clojure projects like Ring I don't really know exactly what dir-path
is for an input
I'm assuming I have to get better (using pattern recognition?) at looking at the let
bindings or destructuring to parse what that input probably was?
the pattern often looks like:
• change (defn f [arg] (frob (munge arg)))
to (defn f [arg] (def f-arg arg) (frob (munge arg)))
• run the code that calls f
• run something like (-> f-arg type)
• based on that, either (-> f-arg keys)
or (-> f-arg first type
etc.
• iterate on thes calls, adding different functions at the end of the ->
chain until I understand what I'm seeing in context
reading the code definitely helps too, but the data in the running app can't mislead you in the ways that sloppy code might
I think that is the direction I have been leaning. Stop expecting to just read code on github and understand it. Actually get my hands dirty in the REPL (well, using my editor) and maybe trying this REBL thing out.
I think I need to do the same
also, there are some idioms of function composition and data destructuring that one simply learns through seeing them repeatedly
It's really not just the "sloppy" code I am struggling with. It is "good" code from well respected Clojure members. I think I just need to work on understanding what the function is doing with the inputs to actually see what the input is if that makes sense. It's just different than when I use other languages and can see ok, well this is a vector of strings.
if you find a question like this for a library, file an issue so the authors can improve the docs. it's totally reasonable to ask for a docstring or more info.
eg. (fn [[k v]] ...)
is an idiomatic function on a key/value pair of a map
Yeah I think upping my destructuring skills will help in a huge way. I'm almost wondering if I should just assume the input is a map until I figure out otherwise.
@U9J50BY4C I definitely have encountered the problem you’re describing, even more so with some JavaScript projects (like React Native’s metro); downloading the project and inspecting it locally definitely helps; familiarity with new language syntax definitely helps but up to a point; understanding certain fn paramaters might only come with a “bigger picture” understanding of the whole project; this is a long-winded way of saying that sometimes there’s just no easy shortcuts to understanding a big project, no matter how experienced you are 🙂
Now that I'm looking back at the docs from my original question: http://pedestal.io/api/pedestal.service/io.pedestal.http.html#var-create-server
I see the map is described in better detail down the page at default-interceptors
all things being equal, the average Clojure project is more understandable to me, because I think most Clojure projects are fundamentally simpler and better structured than the average JavaScript project
I guess I just thought there would be some kind of clojure spec or something in the http namespace codebase
So in what you just posted, William, I am seeing some of the inputs are obj
and it just says this will do something to an obj
. But what is an obj
?! I think I need to get more comfortable with the generic dynamism (?) going on here.
The param I'm referring to is service-map
on the jvm, Object
is the superclass of (nearly) all values, most clojure functions are technically type Object ... -> Object
in context - "edn-response" - obj would be any datum that edn can turn into text
Does clojure spec not provide a way to say that service-map
is going to be map with these keys
it provides a way to say that, spec is inconsistently used and pedestal as a project predates spec by years
ahhh ok gotcha
I just assumed it would be used there if it could be since pedestal is used in tutorials everywhere 🙂
if they added a spec they'd have to account for the previous ways it perhaps accidentally worked
Thanks for the clarification!
I'm glad I didn't steal everyone away from your question William! Thanks for the help everyone. I'm going to keep plugging away
@U015JG247UZ Re: Pedestal -- I've been building web apps and server processes with Clojure for about a decade and I've never used Pedestal. I get the impression it is not particularly beginner-friendly.
What would you recommend?
Depends what you're trying to build -- I don't do any ClojureScript stuff at all (we looked at it about six years ago and decided to go with JS for our front end back then -- a lot has changed since, and if we were starting over we would probably try it again).
Our web apps are mostly just Ring / Compojure (one is Ring / Bidi), running mostly on Jetty (one runs on Netty).
For SSR (server-side rendered) apps, we use Selmer for templating.
Cool I'm writing microservices on jvm primarily right now
We don't even bother with compojure-api, but if we had to generate Swagger/docs we probably would.
We just try to keep our stack nice and simple, so it's easier to reason about and easier to debug.
I'm rewritting an existing api with the goal of simplicity/readability being a high priority.
Right now it's a dropwizard app and requires hunting down docs for days to try and change any of it's core config/behaviors
You kind of just get used to it. You develop a kind of tacit intuition about it. And like other said, you read the implementation code, and try it in the REPL
Function signatures in Clojure can be way more powerful then in other languages. They almost become like mini-dsl of their own. So definitely do expect to spend a bit more time with them
Hi friends! I created a new re-frame project with the leiningen scaffold (`lein new re-frame <app>`). I'm wondering what the proper way of organizing components in different folders / files is.
/src
/clj
/cljs
/circles
/components
circle.cljs
core.cljs
views.cljs
;; components/circle.js
(ns circles.components.circle)
(defn circle []
[:div "circle"])
;; circles/views.cljs
(ns circles.views
(:require
[circles.components :as components]))
(defn home []
[:div [components/circle]])
!!ERROR: Use of undeclared Var circles.components/circle
so you could (:require [circles.components.circle :as c])
and then [c/circles] should work
Are there any best practices around component organization? Or does it vary across projects / organizations
sorry just seeing this. don't fret about it too much. see what issues you run into. i think best practices when you're just starting out can often impede making sure
Is the only way to wait a certain amount of time before calling a function to put it in a thread? I'm trying to find an equivalent to Python's sleep function without having to use threads
(future (Thread/sleep n) (your-code-here))
like that?
(and, yes, that does run the code in a thread)
@chaow I'm not familiar enough with Python to know what the construct is that you're referring to -- can you point me at a code example and/or link?
i tried (future (Thread/sleep 5) (println "hi"))
in a repl, but the "hi" prints immediately
Thread/sleep
is in milliseconds
5 is very short
5000 would be five seconds
Understandable, since the Python sleep()
is seconds.
(I've used other languages that have sleep()
in seconds)