Fork me on GitHub
#beginners
<
2017-09-28
>
dandro12:09:52

Hey everyone I'm new to clojure and trying to pick a stack for my first production project. I was looking at korma for the ORM... does anyone have experience with it? would you recommend it?

donaldball12:09:23

Most clojurists would probably recommend looking at honeysql or hugsql instead of korma if you need a sql interaction library.

schmee12:09:48

korma is heavy on macros, while honeysql uses data, and hugsql uses a different approach altogether

hmaurer12:09:08

Korma’s approach of using a global connection pool seems a bit dodgy to me

hmaurer12:09:28

on top of heavy macros

schmee12:09:49

ugh, didn’t know it did that, that rules it right out for me at least

hmaurer12:09:15

I might be misrepresenting it; I am a beginner and haven’t used Korma, but that’s what I remember from their doc at least

hmaurer12:09:00

> (defdb) creates a connection pool for your db spec (using the wonderful c3p0 library) to make sure resources are used efficiently. It also sets the last created pool as the default for all queries. As such, connection management is essentially taken care of for you.

dandro13:09:50

awesome thanks a lot guys! :thumbsup:

genec13:09:28

is there a leiningen command to rename a project?

genec13:09:57

or do you just edit all the files by hand

chadhs13:09:43

when working with compojure, if you have routes defined in let’s say an project.items.route namespace and a project.lists.route namespace; is there a facility to combine them all?

chadhs13:09:39

like can you call defroutes macro “again” in your core.clj something like (defroutes routes (list-routes item-routes))

astrashe13:09:03

I apologize for this vague question. But is it possible to use a db to persist a function? Let's say you have a function A that gets passed function B as a parameter. Is there any way A can persist B, so that it can be retrieved and used later?

tbaldridge13:09:36

There are some ways, but the better approach is to persist data. So instead of saving the function, save the name of the function, or perhaps an id that can be used to lookup the function in a hashmap

astrashe13:09:25

Thanks -- that's what I do in Ruby 🙂

hmaurer14:09:22

@astrashe what @tbaldridge suggests is how Onyx does it afaik

astrashe14:09:06

@hmaurer Onyx seems really cool, thanks for telling me about it

astrashe14:09:33

I know that I'm going down the wrong road here, that I shouldn't be persisting functions

hmaurer14:09:28

@astrashe Np. Check out https://www.youtube.com/watch?v=kP8wImz-x4w if you have some time. It’s a talk by the creator of Onyx (@michaeldrogalis); he talks about several ways of designing with data, and I think he mentions your issue along the way

hmaurer14:09:11

Off the top of my head, it seems the issue with persisting functions would be: what about dependencies?

astrashe14:09:16

@hmaurer That's really cool, thank you.

hmaurer14:09:37

If the function’s code you are persist is only calling clojure.core functions then it’s fine, but if it’s calling other functions in your codebase you would need to persist those too

hmaurer14:09:41

it could get a bit messy

hmaurer14:09:12

Do you know Datomic?

astrashe14:09:17

No dependencies here. We have a bunch of contracts with various companies, that set up sales targets. I want to use data in our db to figure out if we're likely to hit our targets. A contract could say anything -- our business people make odd deals. 95% of them fall into two kinds of categories. But I'm trying to figure out how to store logic that will decide whether we hit a target in the edge cases

hmaurer14:09:50

Datomic does store functions as code in the database iirc (transaction functions)

astrashe14:09:51

I'd like it to be possible to set it up without adding a new function to the source, but that's probably not a good goal

hmaurer14:09:00

Mmh, in that scenario maybe storing functions directly isn’t too crazy. Don’t take my word for it, I am a beginner too, but if your contract can do anything and yet can be expressed as functions with no dependencies besides clojure.core, then maybe it’s the way to go

astrashe14:09:10

I don't know Datomic, but I'm sure it's worth learning.

hmaurer14:09:12

you wouldn’t want to build a turing-complete DSL to express your contracts

hmaurer14:09:18

unless they follow very specific rules

astrashe14:09:17

I think that letting people type a function into a web interface is probably a bad idea, even if I assume our users are trusted

hmaurer14:09:29

yeah that’s probably terrible 😄

astrashe14:09:34

Someone who can handle writing the function can probably handle modifying the source

astrashe14:09:53

Thanks for the youtube link, I want to watch it

schmee14:09:23

never ever let user enter code that you run!

astrashe14:09:22

Mostly, I was thinking about a problem, and that made me curious about procedure serialization and persistence

tbaldridge15:09:35

@schmee eh, depends on the language. Don't allow them to use arbitrary code from a full language, but that's kindof where DSLs come into play

tbaldridge15:09:27

@astrashe but perhaps allow them to compose predefined operators/functions?

tbaldridge15:09:53

I've used and worked with systems like that before, works really well in conjunction with dataflow programming: https://en.wikipedia.org/wiki/Dataflow_programming

astrashe15:09:37

@tbaldridge The predefined operators/functions approach makes sense. In practice, I think I'll be the person entering the formulas, though.

astrashe15:09:59

The dataflow stuff reminds me of Hoplon's Javelin

tbaldridge15:09:11

Yeah, there are some similarities

tbaldridge15:09:31

spreadsheet dataflow is a subset of the overall concept of dataflow programming

tbaldridge15:09:44

But for a really basic approach, writing a lisp interpreter for simple expressions is quite trivial: (+ 3 (foo "bar")) is easy to evaluate. If it's a number return it, if it's a symbol, look it up and return it, if it's a list, eval the contents and call apply

tbaldridge15:09:22

that gives you the ability to a) store arbitrary code in a database, and b) protects you against "bad code" by only supporting a small number of valid inputs.

astrashe15:09:29

I was curious because old lisps let you save an image of the system, and you can use that to build up data structures that blend code and data in complex ways. I'm not sure how to do that in clojure

tbaldridge15:09:31

Or just store it as a string and call eval, lol

tbaldridge15:09:56

that's not a feature Clojure supports, mostly because the JVM makes it mostly impossible.

astrashe15:09:10

@tbaldridge I think there's an example of a restricted eval function in the land of lisp

astrashe15:09:53

@tbaldridge but the larger question is, let's say you have a db with lots of records. For most records, the value of field1 is just static data. But for a few, it's the result of a function. I think the answer is to just accept that you'll have to put your functions in the source code, and use a schema that will let you figure out what to do

mjcleary20:09:13

If I’m looking for help understanding why a Hackerrank solution is timing out, would this be the best channel?

dpsutton20:09:16

just go for it

rcustodio20:09:41

which is better, map or record?

mjcleary20:09:12

It’s the Maximum Element problem 1 x -Push the element x into the stack. 2 -Delete the element present at the top of the stack. 3 -Print the maximum element in the stack.

(defn get-query [] (map #(Integer/parseInt %) (clojure.string/split (read-line) #" ")))

(defn stack-stepper [query stack]
  (condp = (first query)
    1 (conj stack (second query))
    2 (rest stack)
    3 (do (println (apply max stack)) stack)))
    
((fn []
  (loop [stack '()
         queries-left (Integer/parseInt (read-line))]
    (if (> queries-left 0)
      (recur (stack-stepper (get-query) stack) (dec queries-left))))))

dpsutton20:09:24

general rule of thumb that i've seen is that if you want to implement protocols, use records, else use maps

dpsutton20:09:48

@mjcleary put three backticks ` above and below your code to format it

rcustodio20:09:53

rule of thumb?

rcustodio20:09:02

I dunno that lol

dpsutton20:09:08

general guide, not hard and fast rule,

rcustodio20:09:57

So...

(ns mercadolibre-srv.oauth
  (:require [coremgr-connector.access :as access]
            [coremgr-connector.oauth :as oauth]))

(defrecord oauth [auth credential])

(defn- store->credential [store]
  (first (filter #(= "MercadoLivre" (:type %)) (:credential store))))

(defn status-ok? [status]
  (= status 200))

(defn- valid? [result]
  (and (status-ok? (:status result))
       (= (count (:body result)) 1)))

(defn get [config]
  (let [result @(oauth/generate (settings :username)
                                (settings :password)
                                (settings :client-id)
                                (settings :client-secret))]
    (when (valid? result)
      (->oauth (:body result)
               (store->credential (first (:body result)))))))

(defn get-credential [oauth]
  (when-not (empty? oauth)
    (let [result @(access/credential (-> oauth :auth :token)
                                     (:credential oauth))]
      (when (status-ok? (:status result))
        (:body result)))))
It would've been better with just a map

dpsutton20:09:59

it would be equivalent. there's some readability gains i suppose

rcustodio20:09:16

The hashmap or record?

rcustodio20:09:53

Later I just read it, immutable

dpsutton20:09:31

@mjcleary i notice that your loop has order stack, queries-left but then you recur with the new inputs swapped

mjcleary20:09:59

Maybe I’m missing something, but I don’t see that

mjcleary20:09:50

first expression in recur returns the stack, second decrements queries-left

dpsutton20:09:28

ah you're right

dpsutton20:09:49

it works for me in my repl but i took the unnecessary function call out:

(loop [stack '()
             queries-left (Integer/parseInt (read-line))]
        (when (> queries-left 0)
          (recur (stack-stepper (get-query) stack) (dec queries-left))))

dpsutton20:09:00

does'nt need to be in an anonymous function that is immediately invoked

mjcleary20:09:15

That’s good to know, thank you. I took it out but I still timeout on 11 test cases

dpsutton20:09:27

oh. i only did for a simple case

dpsutton20:09:06

i don't think that changed anything, just made it more readable

dpsutton20:09:25

i modified it to run for a bit longer:

(time (loop [stack '()
              queries-left 10000000
              iteration 1]
         (cond
           (= queries-left 1)
           (recur (stack-stepper [3] stack) (dec queries-left) (inc iteration))

           (> queries-left 0)
           (recur (stack-stepper (get-query iteration) stack)
                  (dec queries-left) (inc iteration)))))
9999999
"Elapsed time: 13762.254026 msecs"
nil

dpsutton20:09:03

changing into a vector shaved off ten seconds:

user> (defn stack-stepper [query stack]
        (condp = (first query)
          1 (conj stack (second query))
          2 (pop stack)
          3 (do (println (apply max stack)) stack)))
#'user/stack-stepper
user> (time (loop [stack []
              queries-left 10000000
              iteration 1]
         (cond
           (= queries-left 1)
           (recur (stack-stepper [3] stack) (dec queries-left) (inc iteration))

           (> queries-left 0)
           (recur (stack-stepper (get-query iteration) stack)
                  (dec queries-left) (inc iteration)))))
9999999
"Elapsed time: 3752.576336 msecs"
nil
user> 

dpsutton20:09:29

@mjcleary note pop and now stack initialized as a vector []

dpsutton20:09:39

changing to a transient did not seem to have improved it much,

mjcleary21:09:37

I’m surprised by the time savings

mjcleary21:09:07

And more surprised I got the same results on the tests once I made those changes

dpsutton21:09:36

meaning the time remained the same as using lists or the time was the same as my results?

mjcleary21:09:52

That the tests timed out (8+ seconds) using vectors like you suggested

mjcleary21:09:00

I really appreciate you looking. More than anything, I wanted to make sure I wasn’t complicating the solution due to my inexperience with FP and Clojure

rinaldi21:09:04

I'm trying to get my head around a problem here and would be nice to get some insights from someone more experienced. I have an API endpoint that gives me back a status on a file upload. Possible values for a status field are in progress and complete. I want to be able to write a function, that returns a promise that resolves only when status is complete, otherwise forces another API hit after an X delay or something. What's the best way to approach this?

noisesmith21:09:00

why a promise?

rinaldi21:09:23

Doesn't have to be a promise, it's just the first thing that came to my mind. Because I never know when the status is gonna be complete.

noisesmith21:09:50

if you use future to start the thread of the upload, you can use realized? to check if it is done

noisesmith21:09:32

in order to check back with another api call you would need to associate that future into eg. a top level atom, perhaps a hash-map with request-id as a key

rinaldi21:09:51

That's true, I can use futures too. My biggest problem though is how to keep hitting the API until I get the value I want.

noisesmith21:09:53

that’s if there’s only ever one server - with multiple servers things get more complex

noisesmith21:09:50

but, if you can use just one server (at least for now) check out atom and swap! and you could have eg. (defonce uploads-in-progress (atom {})) at the top of your namespace

rinaldi21:09:28

It's only one upload at a time

noisesmith21:09:32

then inside your handler (swap! uploads-in-progress assoc unique-id (future (do-upload)) - or something a lot like that

rinaldi21:09:36

I probably can avoid atoms altogether

noisesmith21:09:40

@rinaldi you can’t have multiple clients?

rinaldi21:09:50

Nope, always a single one

noisesmith21:09:13

@rinaldi in order to have the data accessible between two requests you would need an atom, or something like it

rinaldi21:09:36

The upload is taken care by another service, I'm just checking for the completion

rinaldi21:09:47

I don't care about the contents

noisesmith21:09:54

but how do you know which upload to check on?

rinaldi21:09:24

GET /upload-status/:upload-id

noisesmith21:09:46

OK - so really all you need to do is hit the remote api every time that endpoint is hit

rcustodio21:09:48

@noisesmith since you are here, could you help me with this? I'm using record, see below, would hashmap be better? if so why?

(ns mercadolibre-srv.oauth
  (:require [coremgr-connector.access :as access]
            [coremgr-connector.oauth :as oauth]))

(defrecord oauth [auth credential])

(defn- store->credential [store]
  (first (filter #(= "MercadoLivre" (:type %)) (:credential store))))

(defn status-ok? [status]
  (= status 200))

(defn- valid? [result]
  (and (status-ok? (:status result))
       (= (count (:body result)) 1)))

(defn get [config]
  (let [result @(oauth/generate (settings :username)
                                (settings :password)
                                (settings :client-id)
                                (settings :client-secret))]
    (when (valid? result)
      (->oauth (:body result)
               (store->credential (first (:body result)))))))

(defn get-credential [oauth]
  (when-not (empty? oauth)
    (let [result @(access/credential (-> oauth :auth :token)
                                     (:credential oauth))]
      (when (status-ok? (:status result))
        (:body result)))))

noisesmith21:09:29

@rinaldi you mentioned having something that keeps track of when something has completed, you would need an atom for that, but you don’t seem to need that

rinaldi21:09:40

@noisesmith Pretty much, yeah. I'm just struggling on how to keep checking the status until it's complete.

noisesmith21:09:56

oh, so you don’t want to return until it’s complete?

rinaldi21:09:11

Thats why I thought about a promise

rinaldi21:09:36

1. Check progress 2. If complete go to 3, otherwise go to 1 3. Print success

noisesmith21:09:00

(let [p (promise)] (future (do-upload) (deliver p true)) @p) 

noisesmith21:09:07

you don’t need to loop

noisesmith21:09:54

oh wait, you don’t have a call that returns only when upload is done, in that case

(loop []
   (Thread/sleep 1000)
   (if (done)
        true
        (recur)))

rinaldi21:09:18

Oh, I think I can work with that!

noisesmith21:09:53

and you might want to put that loop in a future with the promise etc. in some cases

rinaldi21:09:14

Alright, thanks man, I am gonna give it a shot

lilactown21:09:37

if I have an async operation (e.g. writing a file) that returns a channel; is there a clojure idiom to signal "we succeeded!" or "we failed, here's an error..."?

noisesmith21:09:19

@rcustodio why is it a record?

rcustodio21:09:30

I will just read it later

rcustodio21:09:56

There wont be any new key or changes on it

noisesmith21:09:17

yeah, I’d just use a hash-map instead, there’s no reason to use a record for that

noisesmith21:09:40

the basic concept is that we don’t create new datatypes unless there’s some feature we need from it

rcustodio21:09:43

in erlang record is like struct, clojure is not the same?

rcustodio21:09:54

Immutable and faster reading

rcustodio21:09:17

Always create a new one when it "changes"

rcustodio21:09:50

But if it is the same thing, is it bad for jvm so ppl avoid using it?

noisesmith21:09:45

clojure has different standard design practices than erlang, records are a little faster than hash-maps, but only switch to one if you actually need it

rcustodio21:09:29

And when do we need it?? Protocol?

noisesmith21:09:50

or the rare cases where you need lookup to be faster (the difference is pretty small)

noisesmith22:09:07

I’ve also used them to make profiling easier

rcustodio22:09:45

I see, thanks

rcustodio22:09:16

(ns mercadolibre-svc.rmq
  (:require [langohr.basic :as basic]
            [langohr.channel :as channel]
            [langohr.core :as rmq]
            [langohr.consumers :as consumers]
            [langohr.queue :as queue]
            [mercadolibre-svc.settings :refer [settings]]))

(set! *warn-on-reflection* true)

(defprotocol IRMQ
  (ack
    [this ch delivery-tag])
  (create-channel
    [this])
  (publish
    [this exchange rk payload]
    [this exchange rk payload props])
  (subscribe
    [this prefetch queue cb]
    [this prefetch queue cb options]))

(defrecord RMQ [conn chan]
  IRMQ
  (ack [this ch delivery-tag]
    (basic/ack ch delivery-tag))

  (create-channel [this]
    (channel/open conn))

  (publish [this exchange rk payload]
    (publish this exchange rk payload {}))
  (publish [this exchange rk payload props]
    (basic/publish chan exchange rk payload props))

  (subscribe [this prefetch queue cb]
    (subscribe this prefetch queue cb {}))
  (subscribe [this prefetch queue cb options]
    (let [ch (create-channel this)
          pr (or prefetch (settings :rabbitmq :prefetch))]
      (basic/qos ch pr)
      (consumers/subscribe ch queue cb options))))

(defn rmq [uri]
  (let [conn (rmq/connect {:uri uri})
        chan (channel/open conn)]
    (log/info "connected")
    (->RMQ conn chan)))
in case this is a good choice? (rmq is a state passed though args)

rcustodio22:09:21

Used component as an example..

noisesmith22:09:39

that seems reasonable if performance is critical, or you might make other things that implement IRMQ

rcustodio22:09:53

I understand

noisesmith22:09:57

you can just use functions and hash-maps otherwise

rcustodio22:09:28

In this case, performance is very important

noisesmith22:09:16

@rcustodio the bigger concern around all of this is that if you use functions and vanilla data, things compose and extend quite easily, and you can reuse all the standard functions and idioms of the clojure ecosystem. The more you use custom types with their own methods, the more surrounding code needs to be specially designed for your code. records and protocols are meant to act a lot like hash-maps and functions but there are differences

rcustodio22:09:49

I understand

rcustodio22:09:48

using a hashmap and functions only in this rmq connector would've been more idiomatic, more "clojure"

rinaldi22:09:46

@noisesmith It works!

(defn- response-ready? [campaign-id response-id api-token]
  (let [will-complete (promise)]
    (loop []
      (let [status (fetch-response-progress-status campaign-id response-id api-token)]
        (printf "[%s] Status for response \"%s\" is \"%s\"\n" campaign-id response-id status)
        (Thread/sleep 250)
        (if (= "complete" status)
          (deliver will-complete true)
          (recur))))
    will-complete))

(defn- handle-response [campaign-id survey-id api-token]
  (let [response-id (fetch-response-id campaign-id survey-id api-token)
        response-ready (future (response-ready? campaign-id response-id api-token))]
    (when @response-ready
      (printf "[%s] Zip file for response \"%s\" is ready.\n" campaign-id response-id))))

rinaldi22:09:17

Cheers man

noisesmith22:09:03

so I assume some error handler might set will-complete to false at some point?

rinaldi22:09:21

What do you mean

noisesmith22:09:47

you are checking the value of dereferencing the promise, but the only value it can ever have is true

noisesmith22:09:54

and deref blocks, so it waits until it returns true

noisesmith22:09:37

also, you aren’t actually looking at the value inside will-complete, you are only checking whether will-complete itself is fals or nil - you would need @@response-ready to check the actual value

rcustodio22:09:07

another beginner question... core.async... can it be used for state manegement?

rcustodio22:09:16

instead of record

noisesmith22:09:20

as written right now, you could eliminate the usage of promise completely and just use the return value of loop itself

noisesmith22:09:47

also, as written right now you don’t need a future either (and in fact since you don’t need it, would be better off not using it)

noisesmith22:09:08

the code you wrote loops until complete is true and then prints and returns

rinaldi22:09:54

deliver can also be used to "realize" a future?

rinaldi22:09:10

Otherwise I don't see why dropping the promise

noisesmith22:09:15

@rcustodio records are not state management tools, as they are immutable - something else has to do the actual management

noisesmith22:09:29

@rinaldi deliver isn’t doing anything useful in that code

rinaldi22:09:44

Also I'm using a future there because Thread/sleep would block thread execution until deliver is called

noisesmith22:09:51

nor is the promise - you unconditionally wait on the promise

noisesmith22:09:13

@rinaldi the @ deref blocks

noisesmith22:09:34

nothing you are doing in that code is async

noisesmith22:09:52

you start a future and loop synchronously, and do a blocking wait on the future

noisesmith22:09:06

you’ve effectively wasted a thread because the calling thread can’t do anything until the new thread returns

noisesmith22:09:16

might as well have done the work in the original thread in the first place

rinaldi22:09:59

Oh, you are right. I actually wanted to run the progress check in a separate thread

rinaldi22:09:08

Any ideas on how to improve it?

noisesmith22:09:47

you can use realized? to check if the future is done, if you don’t want to wait and loop

noisesmith22:09:06

but waiting and looping is by its nature blocking

noisesmith22:09:14

it seems like what you need to do is the big picture flow chart of the interactions between the client and your code and the API you use, which things should block, which things should return immediately, etc.

noisesmith22:09:46

once you have a clear picture of the expectations there’s likely a straightforward way to implement it

rinaldi22:09:04

I literally just wanted to keep checking for progress until done, then have a way to signal completeness

rinaldi22:09:18

Would love to do that in parallel so other things don't get blocked

noisesmith22:09:25

so you want to block and not respond until the async thing completes

rinaldi22:09:32

Pretty much

noisesmith22:09:45

in parallel with what?

rinaldi22:09:50

But since this function is gonna be called with different values at the same time, would be nice having it running in a different thread

noisesmith22:09:56

other requests? other actions inside the handling of the same request?

noisesmith22:09:08

called by who?

rinaldi22:09:17

(handle-response) is called by looping over response ids

noisesmith22:09:06

@rinaldi my question is what has to be parallel, whose call needs to be parallel - some consumer of an http api you implement? some caller of your library from other code in the same vm?

rinaldi22:09:14

So in order to check the progress status I hit a vendor API end point, but meanwhile I'm doing other things too (different API calls, in different vendors)

noisesmith22:09:39

all in one function?

rinaldi22:09:44

That's why I wanted to execute this specific job separated... No service relies on each other, so they can run in parallel

rinaldi22:09:54

It's a threading macro

noisesmith22:09:26

(let [upload (future (loop …))] … (realized? upload) …)

noisesmith22:09:40

so the response from vendor1 is passed to vendor2?

noisesmith22:09:44

that can’t be parallelized

rinaldi22:09:00

Sorry, that was a bad example. They do not rely on each oher.

noisesmith22:09:11

then you don’t want a threading macro

rinaldi22:09:59

Yeah, I said threading macro thinking about something else. It's something like:

(let [credentials (fetch-credentials)]
  (vendor1 credentials)
  (vendor2 credentials)
  (vendor3 credentials))

noisesmith22:09:41

(let [credentials (fetch-credentials)
     response-1 (future (vendor1 credentials)
     response-2 (future (vendor2 credentials)]
  {:vendor-1 @response-1
   :vendor-2 @response-2})
runs both requests in parallel, returns when both are available, errors if either errors

rinaldi22:09:04

Gotcha, this part is fine. We were working inside one of these vendors, which require me to constantly check for progress, as you saw.

rinaldi22:09:21

Since they are being called in parallel I guess I could do it in a sequential manner?

noisesmith22:09:24

@rinaldi sure, put the loop inside the future with the vendor call

noisesmith22:09:41

but future itself returns immediately, and all the futures can thus work in parallel

noisesmith22:09:45

but you don’t need a promise in order to do the loop - just return from the loop when you get a positive result

rinaldi22:09:59

When you say return from the loop

rinaldi22:09:11

It would be to replace that deliver call I was making with the actual value?

noisesmith22:09:34

you return from a loop by not calling recur, so yes, you replace (deliver foo true) with true and it just works

rinaldi22:09:13

But if I don't call recur how can I request another status check? That's the part I don't get

noisesmith22:09:29

why would you call another status check if you are done?

noisesmith22:09:38

one branch of an if calls the check, the other returns

rinaldi22:09:49

Exactly, if I'm done I don't need it but until it's done, that could take a few requests

rinaldi22:09:52

Thus the delay

noisesmith22:09:59

you don’t need a delay for that

noisesmith22:09:10

call recur until you are done, that’s it

rinaldi22:09:21

Wouldn't that trigger a shit ton of unnecessary requests?

noisesmith22:09:29

why would it?

rinaldi22:09:29

I see the delay as kind of a debounce

noisesmith22:09:37

there’s no debounce in that code…

noisesmith22:09:47

do you mean the sleep call?

rinaldi22:09:51

I know, it's just the way I see its purpose in the code

noisesmith22:09:15

I have no idea what you think that promise is doing, but I assure you that it does not make your code less agressive

noisesmith22:09:35

you can sleep in a loop without using a promise, and get the exact same behavior

rinaldi22:09:49

I come from JavaScript so I'm still making sense of all these features Clojure gives me to handle async stuff

rinaldi22:09:59

From what you said now I understand it was useless

noisesmith22:09:02

a promise is a mutable container that receives a value exactly once and is afterward immutable - that’s all it is

noisesmith22:09:58

the thing that makes this code async is future - that returns immediately, you can use realized? to ask if it is done, and you can use @ to do a blocking wait for its thread to finish

rinaldi22:09:18

What if instead of recur I wanted to wait 1 second before checking the status again, because of business constraints, API quota usage, etc?

rinaldi22:09:30

Gotcha, that's helpful

noisesmith22:09:39

you already have a sleep inside the loop though

noisesmith22:09:00

I guess you could have two sleeps but I don’t see how it would accomplish anything

rinaldi22:09:01

True, that would do it

rinaldi22:09:16

That's a lot of information, thank you

rinaldi22:09:22

I think I understand it better now

noisesmith22:09:22

@rinaldi one thing that might help your transition is asking yourself which parts of async programming patterns in js are essential to what you are trying to do (eg. the ability to launch a thing and not immediately block on the result) and which parts are accommodating limits that the jvm doesn’t have (attaching callbacks to realization of an async task etc. - things we might also do if our own logic benefits from it but might be simpler to avoid)

noisesmith22:09:40

oh, this is all if you are using jvm clojure of course (I guess I assumed this was the case)

rinaldi22:09:37

I indeed am

rcustodio23:09:29

@noisesmith atom would be for manage state, but async.core can do it as well?? depending how write the app

noisesmith23:09:29

core.async is good at coordination - if you have things that are not synchronized that shouldn’t wait on one another, and you need to coordinate between them in some way

noisesmith23:09:48

so to me it solves some of the things you might solve with shared state in a simpler way

noisesmith23:09:57

but it isn’t a way to manage shared state

rcustodio23:09:02

functional is awesome but at sometimes boring... lol...