clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
2021-01-07T01:11:55.026Z

Is there a way with agent to await-any, like say I want to wait until any one of the agents have finished their queued up tasks, not until all of them have?

2021-01-07T01:15:48.027100Z

Send a task to all the agents that delivers to a promise

2021-01-07T01:17:52.027700Z

You could copy the current implementation of await, except initialize the CountDownLatch count to 1 instead of (count agents)

2021-01-07T01:20:35.029Z

A promise is a countdownlatch(1) internally

2021-01-07T01:42:17.029400Z

Ya, I was thinking that, but hoping there was maybe a core function I was not seeing

2021-01-07T01:43:09.030Z

I think copying the await impl might be better, if I remember correctly, there's a trick with agent to figure out the tasks till the point of calling await, and also having them play nice with STM

2021-01-07T01:43:54.030400Z

Maybe I'm wrong though and its as simple as what you're saying hiredman

2021-01-07T01:46:22.032100Z

I'm just starting to look at agents more seriously, I feel they're a bit of an underdog. Like the more I look into them, the more I feel they can be used pretty simply to model a lot of concurrent, async and parallel scenarios, and you can send-via a custom executor as well, so they give you good control.

2021-01-07T01:48:29.033700Z

I was also thinking, if you do something like:

(defn send-msg [actor-fn message] ...)

(defmulti actor-foo ...)
You can actually use them for actor modeling too. Just create an agent, send it a send-msg partial with the actor. Where send-msg calls the actor-fn with the message and the agent state. And where the actor-fn is just the implementation of the actor.

2021-01-07T01:55:41.036200Z

Another question I had though is, how are the agent scheduled on the Executor? Say I pass them a Single Threaded executor. Will the first agent run all its tasks until its queue is empty before moving to the other agent? Or will they like round-robin?

seancorfield 2021-01-07T02:16:24.036900Z

Re: actors -- I thought the whole point of that model was that they were internally stateful, so I'm not sure how passing an actor-fn would model that?

seancorfield 2021-01-07T02:18:43.037600Z

Oh, you'd encapsulate the state inside the actor-fn? And pass the same (stateful) function repeatedly to the agent?

zendevil 2021-01-07T02:57:41.038700Z

my post request to the reitit routes aren’t working even though the same get request is working

zendevil 2021-01-07T02:57:56.039Z

Here’s the code:

zendevil 2021-01-07T02:57:58.039200Z

(defn home-routes []
 [""
   {:middleware [middleware/wrap-csrf
                 middleware/wrap-formats]}
   ["/" home-page]
   ["/api"
    ["/sign-in"
     (fn [req] (prn "sending response ")
       (r/response "Okay"))]]])

zendevil 2021-01-07T02:58:20.039500Z

and the request is made using http-xhrio

zendevil 2021-01-07T02:58:23.039800Z

like so:

zendevil 2021-01-07T02:58:26.040Z

(reg-event-fx
:sign-in
 (fn [coeffects [_ params]]
   {:db
    (assoc (:db coeffects) :signed-in true)
    :http-xhrio
    (http-post "/api/sign-in"
               {:id "id"}
               []
               []
               #_(fn [] (js/console.log "success"))
               #_(fn [] (js/console.log "failure")))}

   ))

zendevil 2021-01-07T02:58:44.040400Z

(defn http-post [uri params on-success on-failure]
{:method :post
   :uri (str "<http://localhost:3000>" uri)
   :params params
   :on-success on-success
   :on-failure on-failure
   :response-format (edn/edn-response-format)
   :format (edn/edn-request-format)
   })

zendevil 2021-01-07T03:13:35.040600Z

How to fix this error?

πŸ˜• 1
seancorfield 2021-01-07T03:34:02.041300Z

@ps That would be a lot more readable if you used triple backticks around code. But you might consider asking in #reitit

2021-01-07T03:34:25.041600Z

also it would help to know anything about the error

2021-01-07T03:38:10.041700Z

Hum, I didn't think of that, I guess this could work, but no I was thinking just using the agent itself to hold the state. The actor-fn is basically the polymorphic (on the message) dispatching function for the actor. So like, actors have a "mailbox" where they are sent messages, which they process one after the other synchronously (in the order the messages arrive). Each actor has internal state maintained between "sent message". And each actor runs independently from one another and can only be coordinated through message exchanged between them (handshakes and the like).

2021-01-07T03:39:18.041900Z

And the message passing of actors is async, so when you send a message, you get no response, the response must come as a message they might send back to you.

2021-01-07T03:39:42.042100Z

So I feel agent could be used pretty simply to build this model.

2021-01-07T03:40:40.042300Z

But agents don't have "message handling" as part of them. So the actor-fn would be the message-handling-fn for some given actor.

2021-01-07T03:44:06.042600Z

I like the idea of embedding the state in the function though, that's actually what Erlang does. Maybe the associated agent state can store the function for the actor, and as messages are sent to the agent, they are passed to that function which returns a new function.

zendevil 2021-01-07T03:49:08.043Z

there’s no error raised.

zendevil 2021-01-07T03:49:16.043300Z

The request is just not made

saitouena 2021-01-07T03:58:08.046300Z

Is`:sign-in` event dispatched for sure? What about modifying (reg-event-fx :sign-in (fn [_ _] (println "dispatched"))) to check it?

zendevil 2021-01-07T10:09:32.288300Z

yes it’s dispatched for sure

zendevil 2021-01-07T10:09:49.288500Z

this is evident because the same request when done as a get request works

2021-01-07T03:59:03.046900Z

Asking again, since last time I asked it mixed in a wall of text. Anyone knows how are the agent scheduled on the Executor? Say I pass them a Single Threaded executor. Will the first agent run all its tasks until its queue is empty before moving to the other agent? Or will they round-robin? Or something else?

2021-01-07T04:05:44.048200Z

The Clojure reference says: > The actions of all Agents get interleaved amongst threads in a thread pool So I'm guessing this means round-robin in some way? Its not super clear

2021-01-07T04:07:08.048600Z

@didibus pardon my ignorance, in what context would you pass them an executor?

2021-01-07T04:08:10.048900Z

execute uses a final static executor owned by the agent class

2021-01-07T04:08:56.049500Z

oh.. that's in class Action not Agent

2021-01-07T04:09:55.050900Z

You can use send-via which takes a custom Executor. But also, just with normal send it uses a FixedSizeThreadPool, so also wondering in this case how the actions are scheduled on those

2021-01-07T04:10:36.051700Z

(send-via executor a f &amp; args) > Dispatch an action to an agent. Returns the agent immediately. Subsequently, in a thread supplied by executor, the state of the agent will be set to the value of: (apply action-fn state-of-agent args)

2021-01-07T04:10:36.051800Z

right - that file is not so large - each send is submitted to the executor, it decides how to handle the pending ones

2021-01-07T04:10:42.052Z

the executor owns a queue

alexmiller 2021-01-07T04:11:04.052300Z

agents are not scheduled, actions (sends) are scheduled. there is no coordination across agents.

2021-01-07T04:12:09.053400Z

So the Executor handles scheduling them?

2021-01-07T04:12:19.053700Z

right, that's what an executor is

☝️ 1
2021-01-07T04:12:47.054300Z

I see, so most likely they are scheduled in arrival order

alexmiller 2021-01-07T04:13:12.055200Z

"arrival order" is concurrent of course

2021-01-07T04:13:18.055400Z

That means each agent doesn't get an equal share of the ThreadPool

alexmiller 2021-01-07T04:13:28.055800Z

yeah, that's not a thing

2021-01-07T04:13:36.056200Z

Would have been nice πŸ˜›

2021-01-07T04:13:50.056600Z

there are executors that can prioritize submitted tasks right?

2021-01-07T04:14:15.057300Z

I don't know if the send carries anything that the executor could use to impose agent fairness though

alexmiller 2021-01-07T04:14:42.058200Z

what would fairness even mean though

2021-01-07T04:14:42.058300Z

Hum.. I don't know, but also it would need somehow to know which submitted task come from which agent, if it wanted to say round-robin them, and I don't know if it has that information

alexmiller 2021-01-07T04:15:17.059Z

from an executor pov, an action is just a thunk to invoke

alexmiller 2021-01-07T04:15:38.059600Z

it's opaque

2021-01-07T04:15:45.059800Z

Like if I send 10 actions to Agent1 followed by 10 actions to Agent2, and say Agent1 first action takes 10minutes, when its done, it doesn't move to Agent1 second's action, but instead gives Agent2 a chance

alexmiller 2021-01-07T04:16:08.060100Z

yeah, that's not how it works

2021-01-07T04:16:30.060700Z

Basically it would have made the end of an action a yield point

2021-01-07T04:16:43.061Z

Where execution moves to another agent if there is one

2021-01-07T04:17:00.061500Z

why would you be using an executor that doesn't expand or provide multiple threads?

2021-01-07T04:17:29.062300Z

the OS CPU scheduler will mix running tasks

alexmiller 2021-01-07T04:18:02.063500Z

all of the things you seem to want are not what agents intend to provide. maybe you should make your own thing if you want those features

2021-01-07T04:19:22.064600Z

The default Agent Thread Pool is fixed size for example. So if you want to prioritize responsiveness, that could have been nice

2021-01-07T04:20:09.065300Z

then use send-off instead of send?

alexmiller 2021-01-07T04:20:14.065400Z

it's fixed size because it only serves computational tasks

alexmiller 2021-01-07T04:20:22.065700Z

you only have so many cpus that can be doing things

2021-01-07T04:20:26.065900Z

I don't have a fixed goal though, I was kind of just exploring.

alexmiller 2021-01-07T04:20:49.067Z

send-off expands and can do io things

2021-01-07T04:20:50.067100Z

@didibus I think most of what you are concerned with is in the domain of the OS kernel

2021-01-07T04:21:28.067900Z

Ya, but say I compute values and display them in real time, like some gauge. Each in their own agent, but Agent1 keeps hording the pool, so the second gauge response time are hurt

2021-01-07T04:22:19.069300Z

Also, Loom I think will have an Executor that schedules tasks on fibers, so I was wondering if then we could use these with agents.

alexmiller 2021-01-07T04:22:37.069500Z

sure

2021-01-07T04:24:10.071600Z

@didibus I don't know if this matters, but I would think of it as "spamming the executor queue" or "blocking executor threads" more than "hoarding the queue", both of these are mitigated by using a thread pool that can expand and an OS that is smart about CPU scheduling

2021-01-07T04:24:37.072300Z

if you need fine grained allocation of procesisng power and predictable latency, you shouldn't even be using a language with a gc

2021-01-07T04:25:14.073300Z

(depending on how low those latencies really need to be and how bad the worst cases are allowed to spike...)

2021-01-07T04:28:42.077400Z

I see what you mean, but my thinking was that maybe we could use an executor that is backed by fibers. In which case, you wouldn't want agent2 to be waiting for agent1 to be done to kick off your IO, as that would limit your concurrency gains

2021-01-07T04:29:31.078300Z

Hum... or I guess maybe it doesn't matter here, since the fiber itself would yield quickly... hum

alexmiller 2021-01-07T04:29:48.079Z

I mean, ForkJoinPool is designed for exactly stuff like this so you could use that now (but I kinda doubt you'd actually need it)

2021-01-07T04:30:28.079800Z

@didibus waiting on IO actually works great with normal threads, the OS will wake the thread up with a signal when data is ready (or when it's ready for more data) and is smart enough not to check back in until then (unless some other event interrupts your thread, which is usually something like a timeout)

2021-01-07T04:31:07.080400Z

But not with say 1 million IO calls, then you need to multiplex or something

2021-01-07T04:31:13.080700Z

the only disadvantage is the extra bookkeeping data size threads need beyond what fibers provide

2021-01-07T04:31:20.080900Z

Or you run out of memory for all those threads

2021-01-07T04:31:51.081600Z

one thread can wait on N io events, nio allows this doesn't it?

2021-01-07T04:32:36.082500Z

Anyways, I haven't fully wrapped my head around the implications or not of this, but somehow it seems like it be useful (or just cool?) if agents actions round-robinned their use of the ThreadPool with one another.

2021-01-07T04:33:28.083300Z

with core.async you can implement logic for choosing between pending things more directly

2021-01-07T04:33:54.084200Z

but even then usually what you really want is a big list of the things you care about, and you get woken up for the first one that's ready

2021-01-07T04:35:15.085700Z

manifold also has a lot of features for things like throttling and buffering which are effectively ways of managing contention over processing power

2021-01-07T04:35:21.085900Z

Doesn't core.async round-robin go-tasks on the ThreadPool?

2021-01-07T04:35:53.086400Z

Or is it similarly in arrival order?

2021-01-07T04:36:12.086800Z

no actually it tends to check if the channel it just woke up is immediately ready for more work (to reduce coordination overhead in typical usage patterns)

2021-01-07T04:37:00.087900Z

it doesn't wake up go blocks, it serves channels which carry the state machine from your go block as a callback

2021-01-07T04:37:15.088200Z

Hum, that's interesting, I guess cache locality could also be improved with this somewhat, so maybe even for agents, there's benefit to process a few actions for the same agent before moving on to the next

2021-01-07T04:39:56.090200Z

there's actually a bug (or was, maybe it's fixed) in cljs.core.async where there's only one thread available and the logic that checks if the same channel is ready to do more work leads to go blocks that read and write the same channel to hard loop and steal the CPU from all the other go blocks

2021-01-07T04:40:58.092400Z

I know what Erlang does, is that it actually counts how many "task" an actor has been executing, and after it reaches some treshold it'll give execution to another one. That's how it guarantees soft real time

2021-01-07T04:41:11.092800Z

@didibus but anyway, I think the questions you are asking / experiments you might want to try are more in the domain of queues and threads and don't really match up with what agents were designed for

2021-01-07T04:42:11.094100Z

That said, Erlang also kind of makes it that no tasks can be that long, because each iteration in a loop is one task, so if an actor loops 100 times that counts as 100 tasks

2021-01-07T04:42:43.094500Z

erlang isn't good at throughput, it's good at reliability though

2021-01-07T04:42:52.094800Z

and with a decision like that I can kind of see why

alexmiller 2021-01-07T04:43:30.096300Z

if you want a great talk at the insanity that underlies some of the jvm concurrency stuff like forkjoin, I was at this one and it blew my mind: https://chariotsolutions.com/screencast/phillyete-screencast-7-doug-lea-engineering-concurrent-library-components/

alexmiller 2021-01-07T04:44:17.098400Z

like setting threads to doing nonsense work just so they won't go to sleep

2021-01-07T04:44:18.098500Z

Thanks, I'll have a look. ForkJoin is still something I've yet to explore.

seancorfield 2021-01-07T04:44:59.098800Z

WorkStealing... πŸ™‚

2021-01-07T04:45:31.099Z

Ya, but I don't know if the scheduler is the issue, I mean, depending on the configured treshold, it might not yield anymore than the OS will yield your thread.

2021-01-07T04:45:49.099800Z

I think it's the message passing and no global state that adds a lot of overhead to Erlang

2021-01-07T04:45:56.100300Z

now I'm imagining it modeling the behavior of that coworker who always grabs the easiest jira tickets at the beginning of the sprint

emccue 2021-01-07T04:46:00.100500Z

Currently reading through the rant/explanation of EatWhatYouKill and why jetty uses that instead of work stealing

2021-01-07T04:46:03.100600Z

Yielding in Erlang I believe is actually much faster than a thread context switch

emccue 2021-01-07T04:48:10.101600Z

moral of the story continues to be for 99% of things just worry about the semantic structure first and performance second

emccue 2021-01-07T04:48:34.102Z

and leave it to very angry and bitter people to worry about the 1%

2021-01-07T04:49:45.102100Z

taken on its own, out of context this is a political tweet

2021-01-07T04:51:17.103500Z

Another question about agents 😝 If you send, send-off and send-via actions to the same agent, are the order of execution still guaranteed?

emccue 2021-01-07T04:53:53.104600Z

agents all get events "in order"

emccue 2021-01-07T04:54:05.105200Z

so if you are producing events from thread T1

emccue 2021-01-07T04:54:15.105900Z

T1: A, B

emccue 2021-01-07T04:54:26.106600Z

then agents will get the events in order

emccue 2021-01-07T04:54:34.107100Z

if you produce events from two threads

emccue 2021-01-07T04:54:39.107500Z

T1: A T2: B

2021-01-07T04:54:48.107800Z

Ok, so if I send-off something that takes 1 second, then immediately send something that takes 1ms, the 1 second action will still first run and complete before the 1ms action?

emccue 2021-01-07T04:54:52.108Z

then the agents will get events in order w.r.t the threads

emccue 2021-01-07T04:55:28.108400Z

I want to say yes

2021-01-07T04:55:47.109Z

Like I guess I'm wondering if that's true accros Executor, so if you mix send, send-off and send-via

emccue 2021-01-07T04:56:17.109500Z

well, lets look at the source code for this

emccue 2021-01-07T04:57:11.109900Z

volatile Object state;
    AtomicReference&lt;ActionQueue&gt; aq = new AtomicReference&lt;ActionQueue&gt;(ActionQueue.EMPTY);

    volatile Keyword errorMode = CONTINUE;
    volatile IFn errorHandler = null;

final private static AtomicLong sendThreadPoolCounter = new AtomicLong(0);

final private static AtomicLong sendOffThreadPoolCounter = new AtomicLong(0);

volatile public static ExecutorService pooledExecutor =
	Executors.newFixedThreadPool(2 + Runtime.getRuntime().availableProcessors(), 
		createThreadFactory("clojure-agent-send-pool-%d", sendThreadPoolCounter));

volatile public static ExecutorService soloExecutor = Executors.newCachedThreadPool(
	createThreadFactory("clojure-agent-send-off-pool-%d", sendOffThreadPoolCounter));

final static ThreadLocal&lt;IPersistentVector&gt; nested = new ThreadLocal&lt;IPersistentVector&gt;();

emccue 2021-01-07T04:57:16.110100Z

first, gross

emccue 2021-01-07T04:57:56.110900Z

second, we can see that there is a single queue maintained, but two epoch counters

emccue 2021-01-07T04:58:03.111200Z

so lets look at where those counters are used

emccue 2021-01-07T04:59:01.111600Z

emccue 2021-01-07T04:59:09.112100Z

only for making the thread names

2021-01-07T05:00:12.112400Z

cmd)user=&gt; (do (send-off a (fn [_] (Thread/sleep 1000) (println "send-off"))) (send a (fn [_] (println "send"))))
#object[clojure.lang.Agent 0x59429fac {:status :ready, :val nil}]
user=&gt; send-off
send

2021-01-07T05:00:33.112900Z

two different threads, it waited for the long running one before the short running one could start

2021-01-07T05:02:13.114900Z

Ok, so the agent have their own queue. So that mean they somehow wait before submitting the next action from their queue to the executor

alexmiller 2021-01-07T05:02:18.115100Z

(where "order" is ambiguous for concurrent sends from multiple threads)

emccue 2021-01-07T05:02:31.115500Z

I don't know why i prefer detective work to either testing it or asking someone who knows

alexmiller 2021-01-07T05:02:47.115900Z

https://clojure.org/reference/agents says this

emccue 2021-01-07T05:02:51.116Z

both send and send-off call .dispatch

alexmiller 2021-01-07T05:03:00.116300Z

"Actions dispatched to an agent from another single agent or thread will occur in the order they were sent"

2021-01-07T05:03:26.116500Z

By ambiguous you mean due to the thread racing to complete the send invokation?

emccue 2021-01-07T05:04:00.116900Z

dispatch calls dispatchAction

alexmiller 2021-01-07T05:04:05.117Z

yes

alexmiller 2021-01-07T05:04:30.117200Z

but from any single thread's perspective, sends to an agent will occur in order

2021-01-07T05:05:36.117600Z

Ok, ya the reference said that, but it wasn't clear if like it implied from the same dispatch function, like those using send and send-off would be different, or from whichever

emccue 2021-01-07T05:06:09.118200Z

dispatch action either enqueues the action on something called a "LockingTransaction", or on an agent itself

emccue 2021-01-07T05:06:16.118500Z

or on something just called nested

alexmiller 2021-01-07T05:06:18.118700Z

the ordering is enforced by the agent queue, not the executor

emccue 2021-01-07T05:06:19.119Z

final static ThreadLocal&lt;IPersistentVector&gt; nested = new ThreadLocal&lt;IPersistentVector&gt;();

emccue 2021-01-07T05:06:38.119300Z

which has stuff

alexmiller 2021-01-07T05:06:44.119500Z

agents are aware of the STM

alexmiller 2021-01-07T05:07:06.120300Z

and vice-versa

alexmiller 2021-01-07T05:07:32.121800Z

sends that occur during a ref transaction are held and only sent once the transaction succeeds (so after any retries)

alexmiller 2021-01-07T05:07:56.123Z

and sends that occur during an agent send are held and only sent after the agent update completes

alexmiller 2021-01-07T05:08:17.123700Z

the first part is actually an incredibly subtle and helpful tool

alexmiller 2021-01-07T05:08:29.124Z

to let you can do a transaction that does IO

alexmiller 2021-01-07T05:08:42.124500Z

by putting the IO in a send-off agent action

2021-01-07T05:09:18.126Z

Do you know if while STM send actions are held, non STM send actions can go through?

alexmiller 2021-01-07T05:09:43.127100Z

there is no such defined thing

alexmiller 2021-01-07T05:09:56.127500Z

"non STM send actions" is not a thing

emccue 2021-01-07T05:10:15.128600Z

I think he means "things not sent in a transaction"

πŸ‘ 1
alexmiller 2021-01-07T05:10:21.129200Z

you can't do that

raspasov 2021-01-07T05:10:23.129400Z

I found the old Clojure Programming book (the bird one) had very clear explanation of agents, etc

emccue 2021-01-07T05:10:36.130Z

and i don't think a transaction can happen over multiple threads

alexmiller 2021-01-07T05:10:44.130600Z

transactions should only invoke pure functions on immutable refs

raspasov 2021-01-07T05:10:45.130900Z

I haven’t used agents for years, but I remember the book made them very clear when I read it and tried using them

alexmiller 2021-01-07T05:11:04.132100Z

b/c they're pure (no side effects), b/c immutable, the changes can be thrown away

alexmiller 2021-01-07T05:11:31.133500Z

if you do anything else, bad things may happen and it's on you :)

2021-01-07T05:11:38.133900Z

Like if inside a transaction you send to agent1, it hasn't committed yet, and in another thread outside a transaction we send the agent an action. Which action will happen first? Assuming the STM transaction did sent it first but didn't commit yet. Is everything held back in the agent?

emccue 2021-01-07T05:11:39.134Z

so basically that logic is about throwing out stuff enqued from failed transactions?

alexmiller 2021-01-07T05:12:26.135300Z

@didibus there is no ordering defined there

raspasov 2021-01-07T05:12:29.135500Z

@didibus well that’s kinda non-deterministic, isn’t it? πŸ™‚ Depends

alexmiller 2021-01-07T05:12:56.136500Z

@emccue there is no stuff to throw out - the send doesn't occur until it succeeds

2021-01-07T05:13:44.138200Z

Ok I see. So the agent isn't like "blocked" waiting for the STM to commit or rollback. It's just that the sent actions from the STM will happen only when it commits

emccue 2021-01-07T05:13:48.138300Z

yeah but send was called with values - right?

alexmiller 2021-01-07T05:13:59.138800Z

no

alexmiller 2021-01-07T05:14:07.139200Z

send is a function to invoke on the agent's state

alexmiller 2021-01-07T05:14:22.139700Z

like inc

alexmiller 2021-01-07T05:14:36.140600Z

like with all stateful constructs in clojure

alexmiller 2021-01-07T05:15:02.141600Z

it may be a function that closes over state produced in the transaction though

raspasov 2021-01-07T05:15:04.141800Z

@didibus one real-world analogy about agents I had back in the day, and I believe it’s still valid is: think of an agent like it’s a β€œsecret” πŸ™‚ agent that does work for you, but that agent can only do one thing at a time, and only once it finishes with the current task (aka updates its state), it can proceed to the next one; I think it’s as simple as that

alexmiller 2021-01-07T05:15:14.142300Z

yep

emccue 2021-01-07T05:16:16.145100Z

(def a (agent 0))
(def r (ref 0))
(send a inc)
(dosync
  (send a inc)
  (alter r inc))

emccue 2021-01-07T05:16:41.146400Z

this is starting to feel totally orthogonal to @didibus’s questions

emccue 2021-01-07T05:16:42.146700Z

but

emccue 2021-01-07T05:16:59.147700Z

is that an "okay" thing to do in a dosync?

alexmiller 2021-01-07T05:17:01.147800Z

this is an incredible and subtle tool though - you can "send" a database commit from inside a ref that only occurs when the transaction succeeds

alexmiller 2021-01-07T05:17:15.148400Z

the agent's "state" can just be nil and be ignored

alexmiller 2021-01-07T05:17:46.148900Z

sure

2021-01-07T05:17:50.149200Z

Let's talk about send-off instead, since that's the cool thing about agents and STM.

2021-01-07T05:18:06.149600Z

You'll send-off some IO within your STM transaction.

2021-01-07T05:18:54.150100Z

Or at least that's what I understood was the "cool" part?

emccue 2021-01-07T05:18:56.150200Z

so thats what i meant by "does the stuff enqueued get thrown out"

emccue 2021-01-07T05:19:26.150500Z

The transaction itself is keeping a record of what it needs to enque when it finishes

alexmiller 2021-01-07T05:20:07.152200Z

yes

emccue 2021-01-07T05:20:14.152500Z

and if it fails it will "throw out" sends and see what the new run of the transaction will do - right?

2021-01-07T05:20:15.152700Z

right, every time I've used agents it's been because I wanted a thing that acted async, that wouldn't retry, that used immutable data to do some side effect

alexmiller 2021-01-07T05:20:32.153400Z

right

2021-01-07T05:21:15.155500Z

so in retrospect it's now clear how useful they'd be in transactions

alexmiller 2021-01-07T05:21:42.156800Z

the only other reason I've ever used them was to really build a simulation (specifically simulation testing apps)

2021-01-07T05:22:02.157700Z

Hum... Or let me try again. Say I have an STM transaction that runs and sends (fn[_] 10) to agent1 and then the STM literal just goes in an infinite loop because why not. So it never commits. Now say I have another thread that uses an agent as a counter of seconds, and I render the agent count to screen. This other thread loops and sends inc to the agent then waits 1 second, and repeats. Once the STM sends the action, will the agent stop executing the inc actions? Until the STM commits or rollbacks?

alexmiller 2021-01-07T05:22:41.158400Z

if the STM is in an infinite loop there is no send

2021-01-07T05:22:49.159Z

why would the action get sent before the commit?

alexmiller 2021-01-07T05:22:51.159100Z

the send only happens after commit

2021-01-07T05:23:08.159800Z

Ok ok, that is the answer I was looking for.

2021-01-07T05:23:23.160400Z

Just weird that it's the agent that holds onto them and not the ref in the implementation

alexmiller 2021-01-07T05:23:39.160700Z

it's the ref transaction

alexmiller 2021-01-07T05:23:44.160900Z

not the agent or the ref

2021-01-07T05:24:20.161700Z

I might be confused with the implementation. Have to look at it some more

emccue 2021-01-07T05:24:29.161900Z

What is the polite, project priority respecting way to work towards the clojure source code being more legible?

alexmiller 2021-01-07T05:24:54.162900Z

you mean clojure itself?

2021-01-07T05:25:12.163700Z

I don't know, just wanted to be sure it wasn't hehe.

emccue 2021-01-07T05:25:13.164Z

yeah

alexmiller 2021-01-07T05:25:18.164400Z

I dunno

emccue 2021-01-07T05:25:36.165900Z

like, i know no one is complaining about it on the yearly surveys

2021-01-07T05:25:38.166200Z

Like a patch that just refactors things to be more readable?

2021-01-07T05:25:40.166300Z

@emccue maybe a starting point would be a compromise of learning how whitesmith style works - it's not a totally accidental layout

emccue 2021-01-07T05:25:52.167100Z

no, i don't mean the indentation and stuff

alexmiller 2021-01-07T05:26:10.167700Z

there is no chance we would accept such a patch

emccue 2021-01-07T05:26:14.168Z

thats all whatever

emccue 2021-01-07T05:27:04.169700Z

i mean - without running commentary from alex or outside blog posts or a book, i would have had no clue what was going on in the source code with that bit that touched the STM

alexmiller 2021-01-07T05:27:24.170400Z

I would far more interested in improving docs than changing code

emccue 2021-01-07T05:27:33.170900Z

thats what i mean

dpsutton 2021-01-07T05:27:37.171200Z

the clojure reference book by reborg is excellent in this regard

dpsutton 2021-01-07T05:27:54.172Z

(if i follow what you're asking for)

emccue 2021-01-07T05:28:00.172300Z

just the huge doc comments and variable name changes would be a good start

emccue 2021-01-07T05:28:06.172700Z

final static ThreadLocal&lt;IPersistentVector&gt; nested = new ThreadLocal&lt;IPersistentVector&gt;();
`

alexmiller 2021-01-07T05:28:13.173100Z

I mean, the Clojure team has all contributed to the content of Programming Clojure if you want longer form writing than the clojure web site

emccue 2021-01-07T05:28:26.173800Z

oh you mean external documentation

raspasov 2021-01-07T05:28:52.174700Z

@emccue that’s the nature of most software projects, isn’t it; show me any even moderate size library/framework that’s easy to understand for a newcomer; there’s this saying β€œwriting code is easier than reading it” and I think it’s very true

emccue 2021-01-07T05:29:07.175500Z

@raspasov sure

2021-01-07T05:29:27.176900Z

It would be cool if the Clojure code base was in literate style for educational purpose. But I guess I'd find all open source project in a literate style cool for learning purposes hehe

raspasov 2021-01-07T05:29:28.177100Z

I have a hard time understanding most of what’s in React Native, and I’ve been using it since 2016

2021-01-07T05:29:43.177800Z

@raspasov a version of that that I'm particularly fond of: "inventing goes downhill, learning goes uphill"

🀯 1
emccue 2021-01-07T05:29:53.178400Z

compare clojure's PersistentVector class

2021-01-07T05:30:05.179300Z

there's an implicit narrative of how things work and what they mean, that's tied to the process of making it

alexmiller 2021-01-07T05:30:08.179400Z

the chance that I'm going to spend time modifying the source code of Clojure for explicit purpose of making it easier for external devs to read the code is honestly pretty low.

emccue 2021-01-07T05:30:28.180400Z

to the documentation for vavr's bitmappedtrie

alexmiller 2021-01-07T05:30:51.182100Z

if working on it for some other reason and that makes sense to enhance, then maybe

emccue 2021-01-07T05:31:11.183300Z

now that my job is writing clojure and i'm not just a total schmuck, i would be open to dedicating my time to it

emccue 2021-01-07T05:31:44.185700Z

but i am hesitant to do anything for fear that my time would be wasted with a "won't do" on patches

2021-01-07T05:32:07.187400Z

I had an idea to keep a clojure fork with up to date changes but annotated with commentary, maybe do that?

πŸ’― 1
alexmiller 2021-01-07T05:32:09.187500Z

well, I think it's unlikely that anyone that's not on the core team would write the docs that Rich would approve of is low

2021-01-07T05:32:12.187700Z

I think you're better of with a fork where you do that, and then that could be a nice project people can use to learn it. There's still considerable effort from the reviewer, any refactor can introduce subtle behavior change so...

alexmiller 2021-01-07T05:32:39.188500Z

I used to have a fork of core.async that was basically like that for my own sanity

2021-01-07T05:32:39.188600Z

Ya that would be a cool project

emccue 2021-01-07T05:32:40.188800Z

@didibus thats still external docs though

emccue 2021-01-07T05:32:47.189Z

just far less useful

2021-01-07T05:33:24.190100Z

True, but the code base doesn't change that often. So it be easy to be like I don't understand let me check the annotated fork

emccue 2021-01-07T05:33:35.190500Z

> well, I think it's unlikely that anyone that's not on the core team would write the docs that Rich would approve of is low Is that a challenge or a statement on his personality?

alexmiller 2021-01-07T05:34:04.191Z

it's a recognition of the quantity and strength of his opinions :)

2021-01-07T05:34:27.191300Z

πŸ˜‚

alexmiller 2021-01-07T05:34:38.191500Z

as someone that has lived in that crucible for 8 years

2021-01-07T05:35:27.193500Z

The man wrote his own language, I think wanting things in his own peculiar way is probably a little bit part of his personality. And that's also a good thing to have for a designer, it means originality and vision too I'd say.

raspasov 2021-01-07T05:36:04.194500Z

Most of the docs in core are really good; they hit just the right balance between conciseness and exhaustiveness (IMO)

βž• 1
emccue 2021-01-07T05:36:21.195200Z

@raspasov Yeah, but you still program clojure

emccue 2021-01-07T05:36:32.195700Z

there is survivorship bias in that

raspasov 2021-01-07T05:36:41.196200Z

@emccue hahah sure

raspasov 2021-01-07T05:36:47.196700Z

Good point.

2021-01-07T05:37:08.197800Z

I find Javadoc is pretty good as well, and is in a similar style

emccue 2021-01-07T05:37:08.197900Z

the people who i showed clojure to who weren't totally turned away by parentheses really didn't like the docs

raspasov 2021-01-07T05:37:22.198300Z

Hmmm.

2021-01-07T05:37:46.199500Z

Python docs are like a tutorial for beginners. And sometimes I find that's like too much.

raspasov 2021-01-07T05:38:03.200200Z

@emccue https://clojuredocs.org ?

emccue 2021-01-07T05:38:04.200400Z

and personally I still have issues with them that i have trouble putting into words

2021-01-07T05:38:19.200900Z

But the fact http://clojuredocs.org is indispensable might be proof something is lacking in the current doc?

emccue 2021-01-07T05:38:26.201200Z

https://clojuredocs.org/clojure.core.logic/fnc

alexmiller 2021-01-07T05:38:40.201800Z

we have talked about enhancing the docs of the main Java interfaces and I think that's probably the area of greatest interest, in particular understanding how they underlie the clojure core library

raspasov 2021-01-07T05:39:06.202700Z

Well I find http://Clojuredocs.org as an excellent extension; it provides examples, and helpful snippets; You shouldn’t have that in a doc string;

alexmiller 2021-01-07T05:39:07.202800Z

like clojure.lang.Counted <-> counted? etc

emccue 2021-01-07T05:39:13.203100Z

https://clojuredocs.org/clojure.set/rename-keys

alexmiller 2021-01-07T05:39:30.203800Z

I tried to do a pass of that in Clojure Applied -there's a diagram in ch 2 I spent a lot of time on

alexmiller 2021-01-07T05:40:06.205900Z

but the question is always - do you want the team to be writing docs or making more things? :)

emccue 2021-01-07T05:40:11.206400Z

@raspasov there are literally test tools designed to run examples given in documentation

raspasov 2021-01-07T05:40:14.206700Z

@emccue I’d agree clojure.set is not the strongest candidate for perfection; http://clojuredocs.org really saves the day there

2021-01-07T05:40:18.206900Z

Remember that Clojure functions often do a lot though, by nature, they are like mini DSL, and can often take various options, arities and even different types for the same param that can alter behavior.

2021-01-07T05:40:46.207400Z

More things 😝 Though a beginner or newcomer might say more doc

raspasov 2021-01-07T05:41:00.207800Z

https://clojuredocs.org/clojure.set/rename-keys just a quick look through gets me going towards solving a problem

emccue 2021-01-07T05:41:13.208100Z

yeah but look at the examples

alexmiller 2021-01-07T05:41:20.208900Z

clojuredocs is great

2021-01-07T05:41:58.210600Z

Which in turn means it's harder to document. So generally I learn by repl exploration, source reading, doc and clojuredocs notes and examples

alexmiller 2021-01-07T05:42:15.211Z

the docstrings on clojure api functions will never be able to say everything there is to say

βœ… 2
emccue 2021-01-07T05:42:32.211700Z

I vote we strip all the docblocks from java.util.concurrent for Rich

2021-01-07T05:42:55.212800Z

I do wish that higher order function parameters were more clearly described in the doc-strings though

alexmiller 2021-01-07T05:43:01.213200Z

combining those with other sources of information (examples, explainers, source ,etc) is an excellent idea and clojuredocs does it well

alexmiller 2021-01-07T05:43:26.214100Z

the source code in juc is truly world class with some of the best comments I've ever read

2021-01-07T05:43:28.214400Z

I always forget if the function passed to reduce takes the accumulator first or second, and it's kind of hidden within the doc string

alexmiller 2021-01-07T05:43:33.214500Z

gold standard

alexmiller 2021-01-07T05:44:33.215300Z

I have spent an evening or two in my life with glass of scotch reading it

emccue 2021-01-07T05:46:20.217300Z

clojure source needs glasses of scotch for the other reason

2021-01-07T05:47:26.220800Z

Back to agents though. So after all this convo. If I understand, actually the agent won't just submit tasks to the executor as they are submitted to the agent. It'll wait for the prior submitted task to be done before submitting the next one, and keeps actions in its own queue until then. That means that agents actions will be more fairly distributed over the executor pool than I first thought. Which is great.

emccue 2021-01-07T05:50:26.223200Z

> but the question is always - do you want the team to be writing docs or making more things? πŸ™‚

emccue 2021-01-07T05:50:43.223900Z

I think the real answer is - the team should be working on what they want to work on

raspasov 2021-01-07T05:51:09.224800Z

@didibus yes, a task X1 submitted to an agent A1 cannot β€œjump the queue” before all tasks for that agent A1 are completed; only then task X1 gets its turn;

emccue 2021-01-07T05:51:09.224900Z

otherwise they wouldn't work on anything

2021-01-07T05:51:24.225200Z

it helps me to visualize the individual elements of the last arg colliding with the function and accumulated value

πŸ‘ 1
🎩 1
2021-01-07T05:51:36.225800Z

so of course the new element would be the rightmost arg :D

2021-01-07T05:52:25.227800Z

similarly, < and > act as line graphs of the expected magnitude of the args they take

2021-01-07T05:52:43.228800Z

Agents have an unbounded queue, no back pressure, erlang has similar issues with unbounded queues

2021-01-07T05:54:20.229800Z

https://github.com/erlang/otp/pull/481

2021-01-07T05:55:03.231100Z

I was just rewatching ztellmans everything must flow clojure west talk

raspasov 2021-01-07T05:55:31.231600Z

I only try to use (< a b c) and visualize it as a question like: β€œAre a, b, c all increasing?”

raspasov 2021-01-07T05:55:55.232300Z

Consistently only use > or < across a project

2021-01-07T05:55:58.232500Z

Which is much ado about queues backpressure

raspasov 2021-01-07T05:56:01.232600Z

If possible

raspasov 2021-01-07T05:57:11.233100Z

@hiredman that is a good talk

emccue 2021-01-07T05:57:39.233700Z

its worth noting that in the erlang context queues are a distributed thing, maybe across multiple machines

2021-01-07T05:57:59.234100Z

I think having both is useful, for example if my goal is to ensure I haven't reached a limit as a value goes up, I definitely use &lt;

emccue 2021-01-07T05:58:08.234400Z

the analogy in the JVM would be akka's distributed actors

2021-01-07T05:58:21.234700Z

on the other hand, if I am checking a lower limit as something goes down, &gt; is a natural choice

raspasov 2021-01-07T05:58:46.235Z

Fair

2021-01-07T05:59:17.235500Z

at least in english, it's more likely that the first term in a pair of nouns is the one under scrutiny

emccue 2021-01-07T05:59:23.236Z

Clojure's agents are a local thing

2021-01-07T05:59:32.236500Z

Either way, the lack of backpressure is problematic

🎯 1
2021-01-07T06:00:03.237Z

"is an apple better than an orange"? - you are asking a question about an apple

πŸ‘ 1
emccue 2021-01-07T06:00:08.237300Z

to use a minecraft metaphor, you can't fill a hole higher than the top with the dirt you dug out of it

emccue 2021-01-07T06:00:54.238300Z

so OOM on task queues local to a machine feels like it means the machine legitimately has too many tasks to do and not enough time to do them

2021-01-07T06:01:40.239900Z

Yes, which is why you need back pressure mechanisms to reject work

2021-01-07T06:02:12.241300Z

Nothing scales forever, and no scaling is instantaneous

raspasov 2021-01-07T06:02:21.241700Z

@hiredman as an alternative to agents, one can mix core.async primitives, transducers, etc to achieve backpressure; that’s a whole long discussion in itself; but I’d personally use that over agents in most cases, I agree

emccue 2021-01-07T06:02:23.241800Z

I see the queues in front of agents more as the mechanism for making sure things go in logical order

emccue 2021-01-07T06:02:44.242300Z

you can put a regular java bounded queue in front of the logic that actually calls send

emccue 2021-01-07T06:03:31.243300Z

and use the normal mechanisms for backpressure there

emccue 2021-01-07T06:04:02.244Z

if you have distributed actors, that single queue in front is all there is

raspasov 2021-01-07T06:04:13.244300Z

@emccue true, or a core.async channel, that’s a good point

emccue 2021-01-07T06:05:02.244900Z

so things like backpressure need to be solved there

2021-01-07T06:25:05.245900Z

If you have to involve your own workqueues it kind of undermines the case for agents

2021-01-07T06:25:59.247500Z

When an agent dents to another agent, does it also go through this queue? How will the new workqueues interact with the stm and pending sends in agent actions, etc

2021-01-07T06:26:47.247600Z

Huh?

raspasov 2021-01-07T06:30:16.249400Z

That’s a good point as well, it gets more complex; almost depends on the specific use case; in any case, the problem of β€œtoo much work scheduled” should be addressed one way or another (in a well designed system that’s expected to be under heavy load);

2021-01-07T07:29:13.250Z

I'm talking about their underlying Executor. Like imagine they run via a single thread. Agent1 A11, A12 Agent2: A21, A22 ExecutorThread: A11 So we submitted two actions to two agents. We did so synchronously in the order: A11, A12, A21, A22. The question I have now is how is the agents going to schedule those actions unto the ExecutorThread? And from what I understand with everyone's answer from before is that, Agent1 will submit A11 first and then wait for it to be done. In the meantime, agent2 will submit A21 and wait for it to be done. Now, if say A11 is done first, then agent1 will submit A12, and then when A21 is done, agent2 will submit A22. But if say A21 was done first, it would be that agent2 would submit A22 first.

jumar 2021-01-07T07:29:41.250500Z

What is the reason to have both drop and nthrest ? Is it just the different order of arguments?

2021-01-07T19:17:57.309100Z

In Clojure collections are seqable, so you can use sequence functions on them as well. Which is why your examples all work.

2021-01-07T19:19:32.309300Z

Also range here is a special case

2021-01-07T19:20:57.309500Z

(def foo (nthrest (range) 5))
(type foo)
;&gt; cljs.core/Range
(def foo (drop 5 (range)))
(type foo)
;&gt; cljs.core/LazySeq

2021-01-07T19:21:24.309700Z

So nthrest will eagerly return a new range

2021-01-07T19:21:57.309900Z

Where as drop will lazily wrap the existing range in a lazyseq and only once you take from it will it "drop" elements

2021-01-07T19:25:15.312300Z

Try this instead

2021-01-07T19:26:22.313100Z

(def inf
 (iterate
  (fn[a]
   (println a)
   a)
  0)) 

(def foo (drop 5 inf))

; Nothing gets printed cause drop is lazy

(def foo (nthrest inf 5))

; Print 0 five times, because nthrest is eager

jumar 2021-01-08T15:29:33.373500Z

Oh right, so the nthrest is somewhat lazy in that it doesn't force more elements than what it needs while drop is even more lazy where it's not realized at all until you ask for it.

2021-01-08T17:03:05.374Z

Ya, nthrest will be eager up to nth

2021-01-07T07:30:38.250600Z

Yea, in Erlang you put an actor in between which acts as a bounded queue

2021-01-07T07:34:54.250800Z

Cause in theory, you should always assume that the message you are sending is over a network to an actor on some other node

2021-01-07T07:36:06.251Z

But ya, this is one aspect where CSP is nicer, but harder to distribute CSP processes across nodes though.

2021-01-07T07:36:30.251200Z

Thanks I'll have a watch, don't remember seeing that one

2021-01-07T07:39:33.251400Z

I think drop is lazy and nthrest is eager?

πŸ‘ 1
2021-01-07T07:41:01.251600Z

That's why the order of argument differs as well, because drop is a sequence function, so the collection is passed last, where as nthrest is a collection function, so the collection is passed first

2021-01-07T07:42:28.253300Z

Well, "too much" depends on a lot. The load balancer itself can be configured to drop load, so if you've load tested, and configured things accordingly, you might not need backpressure inside your application service, you already know your fleet can handle max load, and that the LB will drop additional load

jumar 2021-01-07T07:43:38.254300Z

This seems to work fine:

(take 10 (nthrest (range) 2))

jumar 2021-01-07T07:45:07.255300Z

Also this:

(first (nthrest (map pr (range)) 2))
012nil

(first (drop 2 (map pr (range))))
012nil

2021-01-07T07:46:24.259Z

I just find agent kind of fascinating. Actors and CSP have been formally studied for capabilities, but I feel Agent are unique to Clojure, cooked in Rich Hickey's brain, and I'd be curious to see someone like formally study their capabilities. What can and can't you model with them? They're an intriguing concurrency construct.

raspasov 2021-01-07T07:48:04.261Z

@didibus yes; This discussion often tends to cross the boundary between the practical/engineering and the theoretical… There’s many ways you can solve the problem, I agree; A purist (I am generally not one of those) would respond to this as saying that it’s β€œkicking the can down the road”; ideally all levels (load balancer, application, etc) would handle backpressure because even if you’ve load tested, performance is probably not entirely deterministic

2021-01-07T07:51:21.263300Z

Ya, in theory you can have certain payload that take different paths than in your load test, and possibly that causes something to be overloaded, but like you said, in practice it's often damn good enough 😝

raspasov 2021-01-07T07:52:47.265300Z

The reality is that majority of applications never get enough usage for all of this to matter… but when it does - it really does… (and I’ve dealt with cases where load is a problem and you are literally at a loss where the problem originates - it becomes a guessing game)

raspasov 2021-01-07T07:54:18.268200Z

In my case it went something like β€œadd more servers to load balancer + increase MySQL capacity till everything works + enough β€œsmart” memcache” (this was not a Clojure system, it was before I even knew what Clojure was)

2021-01-07T07:54:36.268800Z

Well, I wouldn't be surprised if 90%+ of services out in the wild does not perform periodic load testing and scaling exercises either

raspasov 2021-01-07T07:54:59.269200Z

They just don’t need to.

2021-01-07T07:56:13.271200Z

Ya, and if they get a surprise load increase, they'll neither have the LB drop the load they can't handle, nor have any backpressure handling anywhere. Which is fair, your business teams normally only let you put time behind these operational requirement only after they learned the hard way

2021-01-07T07:57:48.273900Z

Some lessons you can only learn the hard way 😝, no amount of senior engineer or engineer "warning" and "I told you so" cuts it, you got to feel the pain to realize

raspasov 2021-01-07T07:58:16.274400Z

Yea, in any case, the simpler the system, the easier it is to reason about; I think here the general Clojure philosophy of keeping things truly simple is worth everything.

2021-01-07T07:59:47.276900Z

I guess to @hiredman's point, if he was referring to core.async or other such thing which handle backpressure by design, I can see that. If you've got something that is as convenient as agent or actor to model concurrency, and it gives you backpressure handling just as easily, you could say it's all around better

2021-01-07T08:00:38.277600Z

Which I know CSP does in some ways.

2021-01-07T08:01:27.279300Z

I still find agent intriguing. I don't know, nobody uses them, but I wonder if part of that is because there's no theory or history around them the same way CSP and actors have.

raspasov 2021-01-07T08:02:27.280600Z

At least core.async has all the primitives to detect when things are getting overloaded; channels have buffers, you can blocking-ly put on a channel (and have that blocking put have a timeout, etc) When your puts start timing out -> Clear signal you’ve reached backpressure

raspasov 2021-01-07T08:08:37.284500Z

I guess the fundamental problem with an agent from a backpressure perspective is that you can just β€œsend” to it - there’s no clear way to infer how big the queue is and if it’s growing, shrinking etc; perhaps there is some hacky JVM way; but there’s no API for it; so no matter how many backpressure mechanism you put in front of a non-backpressure capable construct, once you have to forward your work to the non-backpressure service, all bets are off; the fact that you put a blocking queue BEFORE it doesn’t really tell you much about how the next thing will behave… unless there’s some heuristic mechanism to determine that behaviour… but it gets… complex πŸ™‚

2021-01-07T08:13:28.284700Z

Ya, but it begs the question, could you have a backpressure-send for example? That say counts up to 30 sent task and on the next call awaits them before sending another for example

2021-01-07T08:14:30.285Z

Or could you have a bounded-agent that is similar but with a bounded queue that you can pass in a limit, probably, etc.

2021-01-07T08:14:41.285200Z

So I still feel you could easily build on them

raspasov 2021-01-07T08:14:44.285400Z

You probably could… but it’s just a different API;

raspasov 2021-01-07T08:15:06.285600Z

Because both send and send-off return immediately

raspasov 2021-01-07T08:15:52.285800Z

You definitely could write something else that is effectively β€œbounded” agents with blocking β€œsends”

raspasov 2021-01-07T08:16:09.286Z

Once that upper bound is reached

2021-01-07T08:16:50.286200Z

Ya, I'm imagining it be a new send function that is possibly blocking

2021-01-07T08:17:20.286400Z

Or if using a bounded agent, then send and send-off would still return immediately, but they'd return an error saying you reach the max and need to try again later

raspasov 2021-01-07T08:17:25.286600Z

Yea, but then again, you can recreate most of that functionality in core.async pretty easily

raspasov 2021-01-07T08:18:54.286900Z

Have a global channel with a transducer + an atom; the transducer just updates the state of the atom

raspasov 2021-01-07T08:19:05.287100Z

Basically agents…

raspasov 2021-01-07T08:19:26.287300Z

But also with backpressure capability

raspasov 2021-01-07T08:48:21.287900Z

A little experiment I put together … https://gist.github.com/raspasov/e0081d6de5f61648311c3a598e1f8941

holmdunc 2021-01-07T11:10:45.290200Z

Has anyone else had this happening with the clj REPL? Notice how after pressing return, the previous instance of the prompt (`user=>`) disappears. It happens in both iTerm and http://Terminal.app. I thought my customised ~/.inputrc file might be affecting rlwrap . But moving that file aside made no difference.

2021-01-07T11:25:47.291300Z

clojure command work as expected so this should be connected with rlwrap or clj script itself

holmdunc 2021-01-07T11:36:39.291700Z

Ah - just found this: https://github.com/hanslub42/rlwrap/issues/108 Adding the following to ~/.inputrc is a workaround:

$if clojure
    set enable-bracketed-paste off
$endif
Looks like rlwrap hasn't had a release in almost 4 years. It might be some time before a working-by-default rlwrap is in package managers.

Eamonn Sullivan 2021-01-07T12:21:36.294500Z

For my 10% day at work, I want to set up a basic GraphQL API using pedestal and lacinia-pedestal, but before I start creating from scratch, is there a tools-deps based template for such a thing around? I couldn't find one, but my google-fu may be poor. There's a lien template, but I've become accustomed to deps.edn lately and would like to stick with that.

tzzh 2021-01-07T12:36:15.298100Z

Hey are expired values garbage collected when using a ttl-cache from core.cache ? it seems that the cache still holds the expired values in a way so I want to make sure I am understanding this right as I see there is also soft-cache-factory so I was going to compose the two and do something like

(-&gt; {} 
      (c/soft-cache-factory)
      (c/ttl-cache-factory {:ttl 1000}))
to create the cache and I want to make sure that’s the right way of doing it

alexmiller 2021-01-07T14:35:22.299400Z

Can you add a question and this info at https://ask.clojure.org so others can find it in the future?

alexmiller 2021-01-07T14:36:06.300800Z

If there’s some way to set this on the command line we could bake it into the clj script

holmdunc 2021-01-07T14:37:11.301Z

Ok will do

alexmiller 2021-01-07T15:40:14.302100Z

thx!

holmdunc 2021-01-07T16:12:59.302900Z

The clj script could export INPUTRC=/path/to/some/file/you/provide, but that has potential to cause worse annoyance than the actual bug. e.g. my existing ~/.inputrc file content would then no longer get picked up and I'd no longer have working word-movement (alt-left/right) for my keyboard.

alexmiller 2021-01-07T16:17:51.303100Z

yeah, don't want to do that but rlwrap has a bunch of command line options, not sure if any of those cover

dpsutton 2021-01-07T17:19:44.303600Z

this has been bugging me so much! thanks for asking this question. it made me doubt if i had ever seen the "proper" behavior @duncan540

πŸ‘ 1
holmdunc 2021-01-07T17:30:28.304Z

Yeah, the way I normally use rlwrap+clojure involves the prompt getting coloured green, so it was a bit easier to spot by the scrollback history suddenly lacking any colour

2021-01-07T17:43:16.304200Z

I hope an expert weighs in, but my vague recollection from using the lib / reading the code a while back is that the ttl-cache evicts, but only when you do lookups (maybe insertions?)

2021-01-07T17:43:40.304400Z

also the printed version of the cache can be misleading (even regarding what elements are present)

seancorfield 2021-01-07T18:53:39.304600Z

@thomas.ormezzano core.cache expects the cache to be stored in an atom and when expired values are evicted by an operation, they will be GC'd -- but if you are just looking up values and not doing any additions (or explicit removals), you are just working with an immutable value.

seancorfield 2021-01-07T18:54:56.304800Z

In other words, expired values are only evicted when a new value of the cache is created inside the atom -- swapping out the old value (which still contains expired items) for the new value (which has evicted them).

seancorfield 2021-01-07T18:55:43.305Z

core.cache is a bit counter-intuitive to use (because it's an "immutable cache value") which is why there is core.cache.wrapped which has the same API but automatically wraps the cache in an atom for you.

2021-01-07T19:13:21.307800Z

Ya, but I'm talking about the user interface.

2021-01-07T19:13:46.308Z

Agents give users a different set of capabilities, sometimes they make things easier then core.async. but it all depends

2021-01-07T19:14:23.308200Z

And it's nice that agents let's you choose your own executor as well

2021-01-07T19:14:44.308400Z

But they don't let you choose your queue, which is the issue with backpressure

2021-01-07T19:15:57.308600Z

I might try to add an await-any and a bounded-agent if I have a chance.

2021-01-07T19:16:25.308900Z

I think with that, it open up some nice new use cases for it

2021-01-07T19:23:27.311100Z

I have a sorted-map and i want to replace the values with the reductions of a function over the values, like this:

(let [sm (into (sorted-map) (map vector (range 100) (repeatedly #(rand-int 10))))
      xs (vals sm)
      new-xs (reductions + xs)]
  (reduce conj sm (map vector (keys sm) new-xs)))
is there a more efficient way to do this?

2021-01-07T19:25:26.312600Z

(i guess assoc might be better than conj, and calling the map pair constructor would be better than vector, wondering more if there's another way to approach this though)

2021-01-07T19:26:19.313Z

You want every key in the map to have the same associated value?

2021-01-07T19:27:43.313700Z

no, i want to replace the vals with (reductions + vals)

2021-01-07T19:28:01.313900Z

oh, sorry, I see that now.

2021-01-07T19:28:54.314700Z

I wouldn't be surprised if there are constant-factor time improvements to the code you show above, but nothing that is going to be O(n) faster or anything like that.

2021-01-07T19:29:14.314900Z

gotcha, cool

2021-01-07T19:30:04.315700Z

Sorted maps do tend to be an order of magnitude or so slower than non-sorted maps, because of binary branching rather than usually much higher branching factors in non-sorted ones.

2021-01-07T19:32:22.318100Z

But if sorted map is a requirement for other reasons, then yeah, I would recommend doing a little micro-benchmark between your code above versus an approach that does assoc on each key once, to see which of those two is faster, or whether they are about the same. I don't know of any sorted maps/sets that have transient implementations that might speed this up, neither built into Clojure nor in a 3rd party lib. If this is 90%+ of the run time in an inner loop of your system, then you may want to consider mutable data structures from Java lib instead.

2021-01-07T19:33:54.318500Z

interesting, yeah I'll have to see if I can get away without the sorted map

2021-01-07T19:40:30.320600Z

@jjttjj surely something with (into sm (&lt;some transducer here&gt;) ...)

2021-01-07T19:40:37.321Z

If the time isn't a significant factor in your application, I wouldn't worry about it, as mutability leads to other concerns of application correctness, quite often. Measuring performance, and determining which parts are actually contributing to any perceived slowness, is always a good idea before ripping and replacing data structures.

2021-01-07T19:41:16.321800Z

it's easy to forget that the first arg of into doesn't need to be empty, and most (reduce conj ...) constructs should be into calls

2021-01-07T19:42:02.322500Z

Yeah I guess I was mainly wondering because it felt a little awkward, I don't have an immediate need to improve performance but thought I could be forgetting some core function or something.

2021-01-07T19:42:11.322800Z

@noisesmith oh yeah good call on into

2021-01-07T19:42:56.324Z

It might be faster if you make the map transient, and then use update over the values?

2021-01-07T19:43:33.324800Z

@didibus that is what into is

2021-01-07T19:44:35.326600Z

As an aside, I was trying out the Neanderthal library for double-precision floating point operations on vectors/matrices, etc., and the author has written a nice blog article and Clojure namespace benchmarking about 7 different ways to do a vector dot product. They only differ in constant factors, not big-Oh differences, but I was a little surprised to see the differences were about 250-to-1 for slowest vs. fastest. I wouldn't expect that large a difference for the variations we are talking about above, though. Probably at most 4-to-1 (as a semi-educated guess).

2021-01-07T19:44:49.327100Z

Oh, ok if into already does that under the hood. Wouldn't into be doing assoc into a new transient map though? So it is slightly different than a transient update in an existing one?

2021-01-07T19:45:14.327800Z

@didibus I might have misunderstood you, but the desired result isn't replacing the content under the keys, but deriving new ones to add to it

2021-01-07T19:45:33.328600Z

into uses transients if the into-ed data structure has a transient implementation, yes. Clojure's built-in sorted maps and sets do not have a transient implementation.

1
2021-01-07T19:45:39.328900Z

Oh, ok I misunderstood the problem

2021-01-07T19:45:54.329200Z

transients aren't useful for long term values, they are useful for patterns of building up results in a pipeline

2021-01-07T19:46:43.329800Z

I thought they wanted the values of the existing keys replaced by a new value

2021-01-07T19:47:07.330100Z

They do, IIRC.

2021-01-07T19:47:17.330500Z

If sorted-maps don't have transients though it don't matter

2021-01-07T19:47:57.331700Z

@andy.fingerhut oh good point - I overlooked the fact that literally every key was guaranteed to be replaced

2021-01-07T19:48:13.332400Z

but anyway, into does the right thing regardless, and uses transients when possible

2021-01-07T19:48:30.333100Z

Well, every key will remain in the final result from the original map, but most or all will be assoc'd with a new value.

2021-01-07T19:48:37.333400Z

right

2021-01-07T19:49:35.334400Z

But I am curious of the difference between:

(reduce (fn[m [k v]] (update m k (process v))) (transient existing-map) existing-map)

;; and

(reduce (fn[m [k v]] (assoc m k (process v))) (transient {}) existing-map)

emccue 2021-01-07T19:49:59.334600Z

more GC work in the 1st

emccue 2021-01-07T19:51:00.335600Z

since iirc you need to copy the whole tree for the existing map to make it transient

James Carr 2021-01-07T19:51:14.336200Z

What APM product has really good support for clojure?

James Carr 2021-01-07T19:51:30.336700Z

New Relic and Datadog seem to have some clojure wrappers but they're not great

2021-01-07T19:51:51.337200Z

I think you are mistaken

2021-01-07T19:52:00.337700Z

@emccue The first update or assoc won't copy the whole tree, but since here we know we are changing the value assoc'd to every key, it will by the time it is done.

2021-01-07T19:52:30.337800Z

https://clojure.org/reference/transients

2021-01-07T19:52:30.338Z

yeah, creating the transient is just a boolean field flipped, it's the persistent! call that you pay for

2021-01-07T19:52:36.338300Z

iirc

2021-01-07T19:52:41.338600Z

https://riemann.io/

2021-01-07T19:52:54.339100Z

The first update or assoc on a transient tree-based data structure will only alloc new nodes for the path to that leaf.

2021-01-07T19:53:04.339200Z

that is also incorrect

2021-01-07T19:53:29.339600Z

"In fact, it mostly is the source data structure, and highlights the first feature of transients - creating one is O(1)."

2021-01-07T19:53:48.339800Z

"... you can create a persistent data structure by calling persistent! on the transient. This operation is also O(1)."

2021-01-07T19:54:32.340Z

At least for the vector version of persistent/transients, which I have grokked fully at this point from looking at it too long, you can think of transients as just as "slow" as persistent operations for the very first change, because it allocs just as many nodes as the persistent operation.

2021-01-07T19:54:36.340200Z

There's also https://github.com/metrics-clojure/metrics-clojure which wraps dropwizard

2021-01-07T19:54:57.340600Z

And I just found this: https://www.instana.com/supported-technologies/clojure-monitoring/

2021-01-07T19:55:03.340900Z

The advantage of transients is that all of the newly allocated nodes for the transient are "owned" by the transient, and can be mutated if they are updated again by a later operation on the same transient, before you call persistent!

2021-01-07T19:55:12.341100Z

But I'd go with Riemann :man-shrugging:

2021-01-07T19:55:51.341400Z

The calls to persistent! and transient really are O(1), guranteed, worst case.

emccue 2021-01-07T19:56:14.341600Z

Okay then I'll say the real thing you pay for is in developer understanding

emccue 2021-01-07T19:56:28.341900Z

because i did not know most of that

2021-01-07T19:57:34.342100Z

If you are going to update a significant number of elements in a collection all at once in your code, I can't think of any way using transients could be slower. They can be faster if you do enough operations that "touch" the same internal tree nodes of the data structure multiple times.

2021-01-07T19:58:39.342300Z

If you are doing only one operation each time you do transient then persistent!, you will pay O(1) on those conversions that will be slower, but I'm pretty sure the docs have some mention that they are intended for batches of updates.

2021-01-07T20:00:30.342800Z

So transients are useful if you modify the same element more than once?

seancorfield 2021-01-07T20:01:12.343200Z

We do not use any "Clojure wrappers" -- I didn't even know there were Clojure wrappers for New Relic.

2021-01-07T20:02:10.343400Z

Transients can speed up that case, yes.

2021-01-07T20:02:42.343600Z

because for that sequence of operations, persistents would do path copying in the tree for both 'modify' operations to the same element, but transients won't.

2021-01-07T20:03:41.343800Z

Transients also reduce memory allocation if multiple operations share significant numbers of nodes in the tree paths they are updating. The case you mention of same element more than once is the best "overlap", because the tree path being touched is exactly the same one as the transient touched before.

2021-01-07T20:05:05.344200Z

Any intuition into what else would "touch the same path"? Things like elements that are nearby?

James Carr 2021-01-07T20:05:19.344600Z

I admit, I googled wrt to datadog apm and immediately came across one, but it has had no updates in two years

2021-01-07T20:06:01.344900Z

In Clojure-land, no updates in two years might mean "works great and doesn't need updating"

πŸ‘ 1
πŸ˜… 1
James Carr 2021-01-07T20:06:04.345100Z

the thing I always want to see in APM support for an app is the ability to have custom spans + well supported at tracing libraries out of the box

2021-01-07T20:06:19.345400Z

But as another example, if you create a transient, and the first update walks 4 nodes in a tree to get to the element to be updated, it will allocate 4 new nodes, just as the persistent operation would. If the second operation on the transient was at a leaf that had 3 nodes in common on the path from root to its leaf, and a 4th node that was not shared, the second operation on the transient would (at least usually) mutate the 3 common nodes in place, without allocating new ones, and only allocate a new node for the 4th node that wasn't in common with the first path.

2021-01-07T20:06:34.345600Z

For vectors, indexes that are close to the same as each other.

James Carr 2021-01-07T20:06:38.345800Z

@didibus I admit, I'm a newbie in this space again.

James Carr 2021-01-07T20:06:49.346Z

Rust, golang and python on the teams I previously ran

2021-01-07T20:06:55.346200Z

For hash maps, keys that have hash values close to each other (but that isn't something you typically want to care about in an application)

2021-01-07T20:07:00.346400Z

Especially with wrapper libs. Most likely New Relic doesn't break their APIs all the time. So the wrapper just uses the New Relic APIs, why would it need to change all the time? Its New Relic itself that gets updated

2021-01-07T20:07:05.346600Z

Same for hash sets as hash maps.

James Carr 2021-01-07T20:07:08.346800Z

the last time I fiddled with clojure was in 2011

2021-01-07T20:08:21.347Z

Yeah, the docs are good, if only they were more widely read

πŸ™‚ 1
2021-01-07T20:09:00.347200Z

That's fair. But its true, wrapper libs often don't get many updates, but they mostly still work fine. The only reason to update a wrapper lib is if the underlying lib added new features, or if they broke their existing APIs.

2021-01-07T20:10:13.347400Z

If you are doing any sequence of N operations on some persistent collection all together in time, I think the worst that could happen for doing transients instead of persistent operations are the two O(1) time conversions for persistent! and transient ops. The transient ops will in the worst case allocate as much mem as the persistent ops would, but should never be more, and will in many cases be less.

2021-01-07T20:11:01.347600Z

I'm not sure, but depending upon the reader, they might be left wondering how it works under the hood before they actually believe it πŸ™‚

2021-01-07T20:11:36.347800Z

Makes sense, thanks

2021-01-07T20:12:39.348100Z

For a bunch of conj's growing a vector, transients are amazingly lower at mem allocation.

2021-01-07T20:13:38.348400Z

Riemann will have the best Clojure support of anything, since it is itself implemented in Clojure. Its free and open-source as well. That said, if you want something with like better doc, UIs, etc. New Relic or any other Java based solution should be pretty easy to integrate into your code base. If I were you, I'd quickly glance at some of those wrappers lib you saw, and you'll be amazed how small their code must be, and simple what they do probably is.

2021-01-07T20:14:26.348600Z

Right, and I guess that's where into gets most of its speedup as well

2021-01-07T20:15:06.348800Z

yes, by its taking advantage of transients whenever the target data structure implements htem.

2021-01-07T20:15:30.349Z

Check: https://riemann.io/concepts.html

2021-01-07T20:15:37.349200Z

Riemann is pretty cool though πŸ˜›

2021-01-07T20:17:24.349400Z

But out of transparency, I actually never used it or New Relic, etc. My company has its own internal APM that we use.

seancorfield 2021-01-07T20:22:19.349600Z

This is my (old) blog post, if you decide to investigate New Relic: https://corfield.org/blog/2013/05/01/instrumenting-clojure-for-new-relic-monitoring/

seancorfield 2021-01-07T20:22:44.349900Z

(so at this point we've been using New Relic in production for about 7 1/2 years)

2021-01-07T22:25:58.350800Z

A nice thing about Riemann, I don't know if New Relic is similar, is that you can use it to monitor everything, JVM, AWS services, other programming languages, hosts, logs, exceptions, application level metrics, etc. Since fundamentally, its just a stream processor. You send events to it, it runs some custom filter and route function on it, and then publishes that back to wherever you want, ElasticSearch, Graphite, PagerDuty, etc.

2021-01-07T22:27:08.351100Z

I find its the OpenSource Clojure project that has the most potential to "blow up", kinda like how Scala had Spark blow up for example.

lukasz 2021-01-07T22:53:56.351300Z

The "pull metrics" aspect is not that interesting, storing the data, making queryable and have a decent UX is why people pay for New Relic (or DataDog), rather than hosting their own Graphite+Grafana server - I'm talking about business of course.

2021-01-07T23:00:25.351500Z

Well, anybody could start a SAAS startup offering a hosted Riemann if they wanted. That be neat actually

2021-01-07T23:01:03.351700Z

But you can do all of that with Riemann, but ya, you need to host it yourself. Maybe there's an AWS CloudFormation for it or something though? Or at least someone could probably do one

2021-01-07T23:05:38.352300Z

Like this: https://github.com/threatgrid/kiries

2021-01-07T23:05:57.352600Z

Gives you a ERK stack out of the box: Elastic, Riemann, Kibana

2021-01-07T23:08:07.352900Z

Also, I don't know about New Relic, but Riemann does real-time monitoring

2021-01-07T23:09:10.353100Z

Like if all you have setup is Riemann, and your app and hosts send metrics to it (you can just use one of the existing riemann host and JVM clients if you want). Then you get a dashboard of the real-time metrics from Riemann itself, and you can query its live running index

sP0re 2021-01-07T23:16:51.353300Z

Hi guys! I don't know if it's the right section to write this but, I d like to learn Clojure. I'm a Java developer and I'm going to try a functional language. I had to chose between elixir and Clojure and I think that as Java Dev could have sense to try Clojure. I will start to read "Clojure for the brave and true", is it a good book to start? And what about web development (frameworks, libraries...) and the future of clojure in the industry? Thank you very much

seancorfield 2021-01-07T23:26:09.354100Z

@mircoporetti You'll do better asking for recommendations etc in the #beginners channel -- that's where people have opted in to helping new Clojurians in depth.

πŸ‘ 1
2021-01-07T23:26:42.354200Z

And then you can configure it to "monitor" these real-time metrics, and on some condition have it send something to somewhere, like say sent to slack, pagerduty, email, etc.

lukasz 2021-01-07T23:27:00.354400Z

Correct me if I'm wrong, but riemman only handles collection and short term buffering of metrics, can it do long term storage and integrate with Grafana?

2021-01-07T23:27:23.354800Z

And if you want historical, you can forward events (or filtered events, or pre-aggregated events) to some store like ElasticSearch, Influx DB, Graphite, etc.

sP0re 2021-01-07T23:28:07.355Z

Sorry, I will do it. Thank you

2021-01-07T23:28:26.355200Z

Well, you'd forward your Riemann events to ElasticSearch, and then use Graphana to visualize it

seancorfield 2021-01-07T23:28:28.355400Z

The nice thing about using a commercial service like New Relic is that a) pretty much everything you've mentioned is already available out of the box b) it requires no infrastructure of your own to support it c) you get professional support from New Relic tech folks (and they're really helpful and really good).

lukasz 2021-01-07T23:28:40.355600Z

Right, which means you cannot do what NewRelic offers with just riemman, there's a whole stack needed behind it.

lukasz 2021-01-07T23:29:06.356Z

@seancorfield are you using any of the new offering from NewRelic? They've recently moved off the dreaded per-host pricing

seancorfield 2021-01-07T23:29:43.356300Z

Another possible (commercial) service to look at is Elastic Cloud which offers a full ElasticSearch/Kibana stack with log ingestion etc etc -- which you could use with Riemann I expect.

2021-01-07T23:29:56.356500Z

Well, ya. I mean, its not a Managed hosted solution. So obviously it can't provide that. But its pretty simply to use it to put together your own hosted solution.

2021-01-07T23:30:20.356700Z

Yes, Elastic has a Riemann plugin that can replace logstash

lukasz 2021-01-07T23:30:28.357Z

oh, that's neat

seancorfield 2021-01-07T23:31:31.357400Z

@lukaszkorecki I don't know what we're doing from a price/plan p.o.v. since I don't deal with that side of the house -- I'm a "user" of the New Relic features and, as a developer, an integrator so all of our systems send metrics to it, and are properly monitored.

2021-01-07T23:31:42.357600Z

Hum... nah sorry, thats logstash to Riemann lol

lukasz 2021-01-07T23:31:54.357800Z

Was going to say πŸ˜‰

2021-01-07T23:32:09.358Z

I guess you'd just have Riemann forward things to the ElasticSearch provided to you by your Elastic cloud hosted

lukasz 2021-01-07T23:32:18.358200Z

@seancorfield ah makes sense, we're still a small shop so everyone does a bit of everything. Including cost control 😒

seancorfield 2021-01-07T23:33:22.358400Z

We're small, but I'm lucky enough to be spared some of the day-to-day business minutia. Our front end team is also heavily into New Relic (but they also use Google Analytics and FullStory and...).

2021-01-07T23:33:25.358600Z

https://riemann.io/howto.html#forward-to-elasticsearch

πŸ‘€ 1
2021-01-07T23:33:54.358800Z

Its just:

(def elastic
  (elasticsearch {:es-endpoint "<http://localhost:9200>"
                  :es-index "riemann"
                  :index-suffix "-yyyy.MM.dd"
                  :type "event"}))
(streams
 elastic)

2021-01-07T23:35:14.359100Z

You can also pay for a hosted Graphite, and have Riemann forward to Graphite: https://riemann.io/howto.html#forward-to-graphite