clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
raspasov 2021-03-15T09:22:58.131Z

@lilactown why not just use core.async channels?

1
Adie 2021-03-15T12:51:28.132200Z

How to convert PersistenceList to vector in clojure?

p-himik 2021-03-15T12:54:16.132500Z

What package does PersistenceList come from?

vlaaad 2021-03-15T12:55:47.132700Z

(vec my-persistent-list)

borkdude 2021-03-15T12:57:17.133Z

@p-himik I assume Aditi means the clojure.lang

borkdude 2021-03-15T12:57:29.133200Z

oh wait, that's PersistentList

1šŸ™‚
restenb 2021-03-15T13:04:00.133500Z

(vec the-list)or`(into [] the-list)`

lilactown 2021-03-15T14:23:09.133900Z

because I want to do something else šŸ˜›

lilactown 2021-03-15T14:25:12.134100Z

in all serious, core.async and what I'm fiddling with are different. I used the term "stream" above because I wanted to skip over explaining my exact use case, but what I'm actually building is an incremental computation graph

lilactown 2021-03-15T14:26:26.134300Z

examples of this in other languages are Adapton, Jane Street's Incremental, KnockoutJS/MobX

lilactown 2021-03-15T14:29:17.134500Z

the difference between a channel and what I want is that a channel has no "current" state, while an incremental computation does. with a channel, you block (pull) until you get a message; with an incremental computation, you use the current value to compute your answer, and it will tell you (push) when it has a new value so that you may recompute

raspasov 2021-03-15T15:01:42.134900Z

Hmm, ok šŸ™‚ If Iā€™m understanding correctly, it sounds like you can achieve something similar by combining core.async channels with transducers and one atom per channel which holds the latest state.

lilactown 2021-03-15T15:06:01.135100Z

you could probably create an incremental computation graph built on top of channels and atoms, sure

lilactown 2021-03-15T15:07:34.135300Z

that's sort of unrelated to my question

raspasov 2021-03-15T15:08:37.135500Z

Rightā€¦ šŸ™‚ I just always try to re-invent the wheel less (if possible):

(defonce ch+state
  (let [*chan-state (atom nil)
        ch          (cljs.core.async/chan 1 (comp
                                              (map inc)
                                              (map (fn [x]
                                                     (reset! *chan-state x)
                                                     x))))]
    {:chan-state *chan-state
     :chan       ch}))

(comment
  (cljs.core.async/put! (:chan ch+state) 42)
  @(:chan-state ch+state))

lilactown 2021-03-15T15:10:38.135700Z

I can get deeper into this but just FYI there is a plethora of rich content on incremental computations out there

raspasov 2021-03-15T15:12:03.135900Z

Are you looking into this for front-end or for back-end purposes? (or both)

lilactown 2021-03-15T15:13:19.136100Z

what I'm working on is intended to be CLJC. there are already a few passable solutions for CLJS

raspasov 2021-03-15T15:14:02.136400Z

Nice, I like CLJC šŸ™‚

lilactown 2021-03-15T15:15:39.136600Z

taking your ch+state and assuming you would use add-watch to compose them, you'll run into "glitches" where some atoms will be updated while others are not. there's also problems to solve with computing the graph in an efficient order.

raspasov 2021-03-15T15:16:37.136800Z

I see what you meanā€¦ you could ā€œbufferā€ multiple updates and update a shared atom every N ms, letā€™s say

raspasov 2021-03-15T15:17:02.137Z

Or update one shared atom naively (to start) every time thereā€™s a change

lilactown 2021-03-15T15:17:16.137200Z

it also implies or assumes a particular solution to the problem I posted in the channel, which is that if you deref the state before a "valid" value has been received then it returns nil. I don't know if that's a good solution

raspasov 2021-03-15T15:17:19.137400Z

Thatā€™ll ensure consistency

raspasov 2021-03-15T15:18:16.137700Z

Yeahā€¦ until something has been ā€œdoneā€, I think itā€™s ok-ish to be nilā€¦ I think thatā€™s a fair thing in Clojure, nil signifies something thatā€™s just not there yet

raspasov 2021-03-15T15:18:26.137900Z

Or not there, in general

lilactown 2021-03-15T15:19:34.138100Z

consider the following graph:

(def counter (source 0))

(def a (compute #(deref counter) (remove even?))

(def b (compute #(inc @a))

lilactown 2021-03-15T15:21:31.138500Z

if I initialize a as nil until #(deref counter) returns a not even? value, then when b gets initialized it will throw a NPE

lilactown 2021-03-15T15:22:27.138700Z

I would have to code defensively around this:

(def b (compute #(when-let [n @a] (inc n))))

lilactown 2021-03-15T15:22:37.138900Z

and this would cascade throughout the rest of my computations

lilactown 2021-03-15T15:24:49.139100Z

> Or update one shared atom naively (to start) every time thereā€™s a change this still runs into the problem where between propagation of values through channels, you will see one ch+state update before another. you need a way to update the entire graph and commit it at once šŸ™‚

raspasov 2021-03-15T15:25:20.139300Z

So just so I understand, (compute ā€¦) returns something that is

(instance? clojure.lang.IDeref (compute ...)) => true

lilactown 2021-03-15T15:25:41.139600Z

yeah

raspasov 2021-03-15T15:25:59.139800Z

Same for (sourceā€¦ ) okā€¦

raspasov 2021-03-15T15:26:34.140Z

What Iā€™m suggesting is having one atom shared across the whole graph.

lilactown 2021-03-15T15:26:51.140200Z

yes

raspasov 2021-03-15T15:28:49.140400Z

So youā€™re saying that the whole graph needs to act as ā€œoneā€ unitā€¦. so all computation of all nodes run together, and complete ā€œat onceā€ ?

lilactown 2021-03-15T15:30:38.140600Z

yes exactly. I want it (and the way I've already implemented it) to work is that you make changes to inputs, and the graph recomputes, then updates the state of all nodes at once and then notifies external listeners

lilactown 2021-03-15T15:32:48.140800Z

I'm considering using core.async for the scheduling of computation execution, but using them as the sort of connection between nodes feels like a red herring. it doesn't actually solve any of my problems

raspasov 2021-03-15T15:34:58.142200Z

I understand now šŸ™‚ Cool, I still think that itā€™s possible to achieve the same results with channels + one global atom shared across nodesā€¦ youā€™d just need to schedule/buffer all the change/update functions from all nodes on that global atom and run them all in one (swap! ā€¦) call ā€¦ that would ensure a consistency (I think)

lilactown 2021-03-15T15:37:14.142800Z

I guess I'll say, I've already implemented it with half of what you say - the state of the graph is stored in one global atom shared across all nodes - without core.async. I can send changes to inputs, and this schedules a recomputation of the graph. Each node knows how to identify itself in the graph and recompute its value based on its dependencies.

lilactown 2021-03-15T15:37:22.143Z

why would I want to add core.async into this mix?

raspasov 2021-03-15T15:37:39.143300Z

For sure, sounds like the same idea.

raspasov 2021-03-15T15:37:49.143500Z

I recently learned about something called differential dataflow

raspasov 2021-03-15T15:38:39.143900Z

Might be of interest to you, if youā€™re digging deep into this.

lilactown 2021-03-15T15:39:51.144100Z

thanks for the pointer. differential dataflow definitely falls under the umbrella of incremental computation. I've been reading the literature šŸ™‚

1šŸ‘
paul931224 2021-03-15T15:46:54.144500Z

Hello, is out there any library to visualize clojure code? Not namespaces, function/variable usages. I found some, thought about trying to build one myself, but wanted to ask around first šŸ™‚ something like this: https://200ok.ch/posts/using-clojure-to-visualize-dependencies-in-clojure-code.html

paul931224 2021-03-16T15:03:59.018400Z

well me too, but a city would be hard for first, hope can do something in reagent with svg-s with the gathered data from the analysis.

ghadi 2021-03-15T16:10:06.144800Z

nice list šŸ™‚

p-himik 2021-03-15T16:12:07.145Z

"This list is incomplete. You can help by expanding it." :D

paul931224 2021-03-15T16:13:13.145200Z

yes, thank you very much! I'll take my time reading the source codes. šŸ˜„

borkdude 2021-03-15T16:38:08.146200Z

morpheus and https://github.com/SevereOverfl0w/vizns are both based on the analysis output from clj-kondo, it's pretty easy to get going with that data and write your own tools on top. More info here: https://github.com/clj-kondo/clj-kondo/blob/master/analysis/README.md

2021-03-15T16:46:06.146900Z

if I understand correctly, we are currently looking for a similar solution. for now, we are testing something like this. https://github.com/ribelo/rum/blob/2a41d176f5f62fc96ca40af0d00cf7fa523d5c09/src/rum/atom.cljs

2021-03-15T16:47:46.147200Z

lazy atoms that recalculate according to the viewing hierarchy. we want to get something similar to subscribe with re-frame, but stay as close as possible to pure react

richiardiandrea 2021-03-15T18:22:41.147900Z

FYI this was answered here https://ask.clojure.org/index.php/10347/spec-instrumentation-does-not-work-with-extend-with-metadata

lilactown 2021-03-15T20:34:38.148600Z

yeah that looks nice

lilactown 2021-03-15T20:39:32.148900Z

just reading the source, it looks like it may still have "glitches" occur where updates are triggered before the entire graph has completed; or if an error occurs, it will throw in the middle of updating the graph and you'll end in an inconsistent state

lilactown 2021-03-15T20:42:37.149100Z

you will also end up doing more computations than needed if you find yourself in a diamond dependency graph:

src
  /   \
a       b
  \   /
    c
when src changes, if you do a depth-first re-calculation of the graph, you'll calculate c twice, which probably isn't what you want

lilactown 2021-03-15T20:44:08.149300Z

you can still do a lot of things with those caveats, though. reagent suffers from both of those problems as well and plenty of teams are successful building apps with it