This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-04-16
Channels
- # announcements (1)
- # babashka (23)
- # beginners (157)
- # boot (3)
- # calva (2)
- # chlorine-clover (12)
- # cider (14)
- # clara (5)
- # clj-kondo (6)
- # cljs-dev (61)
- # cljsrn (30)
- # clojure (65)
- # clojure-argentina (8)
- # clojure-berlin (2)
- # clojure-europe (13)
- # clojure-france (9)
- # clojure-germany (2)
- # clojure-italy (4)
- # clojure-nl (6)
- # clojure-portugal (2)
- # clojure-romania (2)
- # clojure-uk (76)
- # clojurescript (56)
- # conjure (52)
- # core-async (37)
- # datomic (209)
- # duct (17)
- # emacs (17)
- # exercism (1)
- # fulcro (26)
- # graalvm (5)
- # instaparse (2)
- # jackdaw (9)
- # jobs-discuss (27)
- # joker (2)
- # juxt (23)
- # leiningen (4)
- # malli (11)
- # midje (3)
- # pedestal (2)
- # quil (2)
- # re-frame (78)
- # reagent (8)
- # reitit (18)
- # remote-jobs (1)
- # ring (2)
- # ring-swagger (1)
- # shadow-cljs (29)
- # sql (11)
- # test-check (12)
- # tools-deps (5)
- # xtdb (16)
- # yada (4)
async vs sync
What is the proper way of composing signals? Intuitively, I would like something like Monad in Haskell. @(compose-signal (subscribe {:a}) #(subscribe [:b %))
Something that works like >>=
in Haskell
I feel like neither reg-sub
or reg-sub-raw
does that, it rather "aggregates them"
For the people not familiar with Haskell, what does >>=
do?
Do you mean something like
(let [a @(subscribe [:a])
b @(subscribe [:b a])]
...)
?yes, something like that, but I think re-frame doesn't allow that, because a
would need to be deferred?
oh nvm, you deferred it, it wasn't paying attention
Oh yes my bad
Your code works indeed but it removes the potential for laziness. b
cannot be computed if a
is not deref-ed.
How I imagined it is the signal chain can be composed until the final value is needed, without any intermediate deref
Because of how Reagent works, you cannot possibly compute b
without computing a
first.
But I compose b
with a
just how I can compose two functions without having a value
The subs are not functions, far from it. The only common thing is that there's input and output, that's it.
With functions, it's the caller who's responsible to call the function again if the input changes. With subs, they do it themselves - changes to any of the subs derefed in a view will result in view rerendering.
Indeed, but I'm still not sure how signals cannot be composed. Surely if signal b depends of signal a, I don't need to know the value of a to manipulate b
I will need to know @a
whenever I need to use @b
You can compose subscriptions together and can deref the composite subscription. Look at the bottom of this file: https://github.com/day8/re-frame/blob/master/examples/todomvc/src/todomvc/subs.cljs Is this what you want? (I have no idea what a Monad is)
@U0111PVCS8P That's the thing - you will need to use @b
. Maybe not right where you subscribed to it. But every call to subscribe
must be followed by a call to deref
at some point.
> Surely if signal b depends of signal a, I don't need to know the value of a to manipulate b
You - don't need to, right. Reagent - does. b
cannot exist without a
.
You mean :<-
right @UJRDALZA5?
@UJRDALZA5 The subscriptions signals and the :<-
sugar might solve the problem for the simplest cases (and I admit - the case in the OP is one of them). But it doesn't solve the problem in the general case.
@U2FRKM4TW but @b
will trigger the deref of a
No, I retract that - the OP case is not the simplest since :b
doesn't even know about :a
.
No because re-frame
doesn't allow it or because it's computationally impossible?
I could write the compose-signal
function myself, right?
Damn, apparently I don't know it myself - I totally mixed it up with run!
, sorry about that.
Yes, you're correct. That is, if I understood you correctly. If b
is a reaction that depends on a
, then @b
will trigger @a
.
But there won't be any magic, right? I still need to define compose-signal
?
Yep. It can even be a macro. Either way, you will end up creating an extra reaction on top of a
and b
.
I see. Thank you!
So if I'm imagining it correctly (and as you can surely already tell, it's very likely that I'm not 🙂), you will trade the requirement of writing extra @(subscribe)
characters for the (compose-signal)
characters and maybe an extra reaction.
Right now, I don't see the appeal of such an exchange.
If you come up with something that you find satisfactory, please share if you can - I'm curious.
Well the idea is to come up with some way of introducing laziness to the equation, but I'm not sure it's possible nor derisable
Apart from reducing code size, ofc
I agree. Lazyness is useful to spread out the work and to avoid doing the unnecessary work. And when you render a component, both fly out the window because you need all of the data, right now.
Code side reduction is nice only if sacrifices, whatever they may be, are worth it. In this particular case, you probably sacrifice flexibility because the interface of compose-signal
will likely be much more limiting compared to what you can do with two plain vectors and a value.
Well the same could be said for threading macros or comp
, it still exists because it's practical in some cases.
I've been thinking and experimenting a lot with re-frame code bases that I have. And after about 3 years, I've made only 3 main things:
1. Create a macro that automatically creates calls to reg-sub
for trivial cases of (get-in db [:a :b :c key])
or something similar
2. Use my own reg-event-*
functions that all have the same base interceptors
3. Stop splitting events, subs, and views in different files. One component - one file, roughly. None of those events.cljs
and co.
About laziness increasing the data lag, I'm not sure this is true
comp
is still being debated by the users of Clojure.
->
and co are useful in a vast number of cases simply because of how Clojure functions tend to position their arguments.
You said, laziness is less useful in a render scenario, because "you need the data right now". I don't think eagerness is any better. In practice, I don't see how it should affect performance.
Lazyness is not able to improve the performance in the Reagent views. Lazyness can potentially improve performance in some other cases.
Why not?
To be clear, I'm talking about the case when you eventually consume all the data. Because Reagent will consume all the data right away anyway.
Of course one could maybe do something incredible with setTimeout
. But let's not dwell on it too much. :)
Reagent will consume the data whenever I call @
, I think
Right, and if you have multiple @
in a single component tree, they will all be executed.
So not using @
is akin to just not calling a function that produces some not lazy data.
Not sure I am following you
Or myself
tl;dr: it's not possible, to the best of my knowledge, partially consume lazy data with Reagent. I've seen people much more experienced than I am in these matters advise to abstain from any lazy collections at all in Reagent because in come cases it makes it incredibly hard to debug, IIRC.
Of course!
@U2FRKM4TW fyi, wrote this:
(defn chain-reaction [f r]
(r/reaction
(let [v @r]
(if (and v (not (and (map? v) (:status v))))
@(f v)
v))))
the if condition is just a particular need
Ah, so it ended up not being related to re-frame at all. Can you give a couple usage examples?
yeah, (->> sig-a (chain-reaction f) (chain-reaction g)
will return the composed signal of f applied to g applied to a
Sorry, I meant a more practical one, especially if you use that function in re-frame with subscribe
.
well let's say you have to chain queries, you could do somthing like this:
(->> (subscribe [:a}) (chain-reaction #(subscribe [:b %})))
hate typing clojure anywhere else than in my editor err
(ofc the code I need to write depends on more than 2 signals)
reduce could be used with chain-reaction, also
I think I almost never see such a pattern have more than, say, 2 levels in my code. Probably because I tend to use small UI components, or maybe because your code has some particular requirements, dunno. In any case, it feels a bit strange that for N subs you create N - 1 reactions on top of them.
Not sure I'll end up using it, we'll see
But it was an useful exercise in understanding how reactions work for sure
i have a conceptual question i hope someone can answer pretty easily: technically, could i call @(rf/subscribe [:foo])
anywhere in the codebase? in a component, in a handler, or just in any plain fn? there’s nothing special about the sub except that calling it inside a component means it will be cleaned up, and calling it anywhere else means it will dangle/leak memory over time?
i.e. i want to make sure there’s no fancy introspection going on inside a component that means a subscription deref wont trigger if the subscribed value changes, if i deref it in some random function.