Fork me on GitHub
#beginners
<
2020-06-10
>
Purujit Goyal00:06:24

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

Purujit Goyal00:06:02

This works if I change api to routes, but swagger starts failing after that

Purujit Goyal00:06:56

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?

seancorfield01:06:46

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

Purujit Goyal01:06:28

I have updated the code, removed unnecessary stuff, removed :all and fixed the paranthesis

seancorfield01:06:03

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

seancorfield01:06:23

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

Purujit Goyal01:06:28

okay, thanks for the response, will wait for them

Ian Fernandez01:06:15

(s/def ::a (s/keys :req [::b] :opt [::c])) if I want to have or :opt [::d] or :opt [::e]

Ian Fernandez01:06:01

how I proceed?

Ian Fernandez01:06:47

I'm having this error

Ian Fernandez01:06:47

Assert failed: spec/or expects k1 p1 k2 p2..., where ks are keywords (c/and (even? (count key-pred-forms)) (every? keyword? keys))

Ian Fernandez01:06:57

didn't understood =(

seancorfield01:06:04

@d.ian.b You need to share the actual code that produces that error, otherwise we can only guess what you're doing...

Ian Fernandez01:06:18

er, proprietary ^^'

seancorfield01:06:51

Then create a non-proprietary repro that you can share.

Ian Fernandez01:06:44

it's reaaaally difficult to anonimize this case

Ian Fernandez01:06:06

I was trying something like

seancorfield01:06:23

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

Ian Fernandez01:06:35

(s/def ::a (s/or (s/keys ,,, (s/keys ,,, (s/keys ,,,

Ian Fernandez01:06:43

but not working

seancorfield01:06:50

Also, use triple backticks around your code so it's easier for us to read 🙂

seancorfield01:06:12

OK, s/or requires a label for each variant. See the examples on http://clojure.org

Ian Fernandez01:06:21

I thought that it's reaally simple the example

Ian Fernandez01:06:31

to not have triplebacktick

Ian Fernandez01:06:37

thanks, I'll see

seancorfield01:06:48

You can also use single backticks for inline code.

Ian Fernandez01:06:06

most part of the day I use prismatic/schema

alwyn02:06:17

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?

seancorfield02:06:00

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

seancorfield02:06:38

Does the difference make sense to you?

Ian Fernandez02:06:38

@seancorfield that or stuff was a way too much complex for the stuff what I thinking that I had to do 😃

Ian Fernandez02:06:51

I solved in the before specs 😃

alwyn03:06:46

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

alwyn03:06:08

using clojure 1.10.0

alwyn03:06:38

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

seancorfield03:06:44

You can "blame" CIDER for that, or rather nREPL I suspect -- it separates results and output.

seancorfield03:06:01

In the standard REPL, you can see the difference because output and results are interleaved.

seancorfield03:06:57

(even unrepl does this I think -- and prepl is designed to do it too)

alwyn03:06:06

When I use leiningen to run the repl it uses nrepl with the same results, how do you start yours?

seancorfield03:06:10

I haven't used Leiningen for years. I switched to Boot in 2015 and then to the Clojure CLI / deps.edn in 2018.

seancorfield03:06:04

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

alwyn03:06:15

ok that shows the difference even better

seancorfield03:06:18

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.

seancorfield03:06:04

(it's still not interleaved, but at least you see six prints with the non-lazy version and just three with the lazy version)

alwyn03:06:38

yes, still interleaved, but its like you say

alwyn03:06:57

I guess with practice you learn the quirks of your repl 🙂

alwyn03:06:27

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?

seancorfield03:06:06

Yes, because it has to realize enough elements to decide whether they are equal or unequal.

seancorfield03:06:18

;; 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=>

seancorfield03:06:33

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.

seancorfield03:06:14

Only one call is needed in the second example because it returns (2 ...) which is not equal to (1 ...) in the first element.

alwyn03:06:41

my terminology might not be idiomatic, but does that mean that comparison is in a sense overloaded to support the sequence abstraction?

seancorfield03:06:17

https://clojure.org/guides/equality is probably the best thing to read about that.

seancorfield03:06:41

TL;DR: Clojure has "value equality".

💯 3
alwyn03:06:42

thanks for the insights, time to zzzz on it

💤 8
jaihindhreddy03:06:01

^ Henry Baker's "Equal Rights for Functional Objects" is a good paper to check out in that area.

ikitommi04:06:49

@puru072 the api already has a JSON middleware in it, I believe.

Purujit Goyal23:06:57

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

seancorfield04:06:26

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

ikitommi06:06:32

The record implements IFn with 1 & 3 arities, works just like a ring handler.

seancorfield06:06:24

Ah... OK. Definitely not clear from the docs. Any idea why @puru072’s example doesn't work?

Jason Lee08:06:30

anyone know how to encode url in clojure for example replace space to %20?

gon08:06:48

you can use Java directly

java.net.URLEncoder

Chris Swanson08:06:31

or cemerick/url

Chris Swanson08:06:52

I'd just use the Java directly too though

tees12:06:19

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

alwyn12:06:24

Would using group-by on :level make it easier? (Assuming one can add something to indicate the order in the specific level?

andy.fingerhut12:06:14

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

alwyn12:06:57

Indeed, the same level can also occur on different 'branches'

Matej Vasek12:06:04

partition-by maybe?

andy.fingerhut12:06:58

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.

tees13:06:53

this makes sense. I was thinking reduce might be the way I have to go.

tees13:06:39

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.

andy.fingerhut13:06:59

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.

andy.fingerhut13:06:37

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.

alwyn14:06:09

Sounds like a candidate for 4clojure

tees14:06:50

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

andy.fingerhut17:06:17

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.

andy.fingerhut17:06:41

loop/recur does not have that limit

tees18:06:49

Got it. Thanks again!

Jim Strieter12:06:52

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.

andy.fingerhut12:06:58

Does some combination of line-seq and drop do what you need?

Jim Strieter12:06:21

I'll take a look at that, thanks!

Jim Strieter12:06:21

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.

andy.fingerhut12:06:59

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.

andy.fingerhut12:06:26

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.

andy.fingerhut12:06:42

Or if you somehow know that all lines have a particular length in bytes.

Jim Strieter12:06:40

Oh I see. You need to parse the whole thing so until you've counted the correct number of \n's.

andy.fingerhut12:06:05

Right, unless you know more about the structure of the file than simply 'it is a text file'

Jim Strieter12:06:47

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.

andy.fingerhut12:06:10

There's a reason why people prefer databases for large data 🙂

andy.fingerhut12:06:05

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.

andy.fingerhut12:06:28

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.

Alex Miller (Clojure team)13:06:13

Java has support for every version of this you might want, either via streaming Readers, RandomAccessFile, or memory mapped files with MemoryMappedBuffer

Alex Miller (Clojure team)13:06:41

Clojure does not really wrap any of that stuff but it's trivial to use via interop

Jim Strieter15:06:49

Yes, people prefer databases for large data

Jim Strieter15:06:17

RandomAccessFile sounds like the thing I would want

Jim Strieter12:06:00

Andy & Alex - thank you for taking the time to answer! I appreciate it 🙂

Jim Strieter12:06:21

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.

Eric Ihli13:06:14

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.

Alex Miller (Clojure team)13:06:42

no, unless you want to completely (shutdown-agents) (but that will prevent all futures from working until you restart the jvm)

Petrus Theron13:06:57

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)

❤️ 28
Lukas19:06:05

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.

Lukas15:06:41

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 😇

ghadi15:06:31

you can apply a transducer directly to a channel: (async/chan 1 (distinct))

ghadi15:06:13

distinct returns a transducer that does the transformation you desire, I think

Alex Miller (Clojure team)15:06:02

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)

☝️ 7
Lukas15:06:30

Thanks guys :hugging_face:

mathpunk16:06:40

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

Alex Miller (Clojure team)16:06:07

not clear in what way that would help you

Alex Miller (Clojure team)16:06:58

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)

4
mathpunk16:06:44

Cool. The existence of io/resource made me think, I'm supposed to treat paths that are program input in a special way

noisesmith16:06:22

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

noisesmith16:06:46

it's meant for the kind of thing that the app carries with itself

mathpunk16:06:25

thanks, that makes sense!

Eric Ihli17:06:59

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

noisesmith17:06:56

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

noisesmith17:06:18

(sleep or wait on IO, more or less)

noisesmith17:06:36

otherwise, the task can check if the thread it is in is cancelled and exit gracefully

noisesmith17:06:55

but there's no reliable way to simply kill it from the outside

Eric Ihli17:06:12

Ahhhh. Thanks! That sounds exactly like it would cause the issue. The subtasks I'm running are process-heavy, not sleep/io.

noisesmith17:06:27

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

noisesmith17:06:38

future-cancel will make that return true

Rameez17:06:11

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?

Alex Miller (Clojure team)17:06:20

(map (fn [[[y m] a]] {:year y :month m :amount a}) data)

👍 8
dpsutton17:06:58

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.

Rameez17:06:05

Hi! Thanks for pointing that out! 🙂

Rameez17:06:17

I actually got here by learning "group-by"

Rameez17:06:41

But you're right, its not sorted by month.

dpsutton17:06:25

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

William Skinner17:06:55

How to people typically figure out what fields are available in large config maps (aside for online docs)? For example, the Pedestal service map.

seancorfield17:06:06

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

Chase18:06:39

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)

seancorfield18:06:11

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

Chase18:06:20

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

Chase18:06:12

Can I piggieback of this question to a more general level on understanding function inputs? It's by far my biggest struggle with Clojure.

Chase18:06:43

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.

Chase18:06:55

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.

Chase18:06:16

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

noisesmith18:06:32

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.

noisesmith18:06:28

I often end up resorting to capturing the data inside the function call, and then investigating it in the same manner @seancorfield describes above

Chase18:06:12

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

Chase18:06:43

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?

noisesmith18:06:05

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

noisesmith18:06:40

reading the code definitely helps too, but the data in the running app can't mislead you in the ways that sloppy code might

Chase18:06:37

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.

William Skinner18:06:58

I think I need to do the same

noisesmith18:06:01

also, there are some idioms of function composition and data destructuring that one simply learns through seeing them repeatedly

Chase18:06:03

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.

Alex Miller (Clojure team)18:06:22

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.

👍 4
noisesmith18:06:24

eg. (fn [[k v]] ...) is an idiomatic function on a key/value pair of a map

Chase18:06:14

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.

raspasov18:06:41

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

William Skinner18:06:59

I see the map is described in better detail down the page at default-interceptors

raspasov18:06:14

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

William Skinner18:06:26

I guess I just thought there would be some kind of clojure spec or something in the http namespace codebase

Chase18:06:58

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.

William Skinner18:06:04

The param I'm referring to is service-map

noisesmith18:06:19

on the jvm, Object is the superclass of (nearly) all values, most clojure functions are technically type Object ... -> Object

noisesmith18:06:15

in context - "edn-response" - obj would be any datum that edn can turn into text

William Skinner18:06:18

Does clojure spec not provide a way to say that service-map is going to be map with these keys

noisesmith18:06:09

it provides a way to say that, spec is inconsistently used and pedestal as a project predates spec by years

William Skinner18:06:00

I just assumed it would be used there if it could be since pedestal is used in tutorials everywhere 🙂

noisesmith18:06:09

if they added a spec they'd have to account for the previous ways it perhaps accidentally worked

William Skinner18:06:02

Thanks for the clarification!

Chase18:06:55

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

👍 4
seancorfield18:06:41

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

William Skinner18:06:24

What would you recommend?

seancorfield18:06:07

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

seancorfield18:06:07

Our web apps are mostly just Ring / Compojure (one is Ring / Bidi), running mostly on Jetty (one runs on Netty).

seancorfield18:06:37

For SSR (server-side rendered) apps, we use Selmer for templating.

William Skinner18:06:11

Cool I'm writing microservices on jvm primarily right now

seancorfield18:06:53

We don't even bother with compojure-api, but if we had to generate Swagger/docs we probably would.

seancorfield18:06:17

We just try to keep our stack nice and simple, so it's easier to reason about and easier to debug.

William Skinner18:06:12

I'm rewritting an existing api with the goal of simplicity/readability being a high priority.

William Skinner18:06:14

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

didibus19:06:11

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

didibus19:06:56

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

coetry21:06:17

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

coetry21:06:39

I keep running into !!ERROR: Use of undeclared Var circles.components/circle

coetry21:06:51

If I need to publish a repo so its easier to debug, lmk

dpsutton21:06:04

there is no namespace circles.components defined here

dpsutton21:06:17

there's circles.components.circle which has a var circle in it

dpsutton21:06:41

so you could (:require [circles.components.circle :as c]) and then [c/circles] should work

coetry21:06:38

Ahh I see, that seems to have worked :thumbsup:. Thank you

coetry21:06:05

Are there any best practices around component organization? Or does it vary across projects / organizations

dpsutton21:06:33

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

👍 4
🙏 4
chaow22:06:39

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

seancorfield22:06:26

(future (Thread/sleep n) (your-code-here)) like that?

seancorfield22:06:46

(and, yes, that does run the code in a thread)

seancorfield22:06:52

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

chaow22:06:56

print("Printed immediately.")
time.sleep(2.4)
print("Printed after 2.4 seconds.")

chaow22:06:20

i tried (future (Thread/sleep 5) (println "hi")) in a repl, but the "hi" prints immediately

seancorfield22:06:35

Thread/sleep is in milliseconds

noisesmith22:06:38

5 is very short

seancorfield22:06:41

5000 would be five seconds

chaow22:06:49

i thought it was seconds my bad

seancorfield22:06:10

Understandable, since the Python sleep() is seconds.

seancorfield22:06:25

(I've used other languages that have sleep() in seconds)

chaow22:06:26

yea wrong of me to assume though, its working like a charm now, thanks!