Fork me on GitHub
Quentin Le Guennec15:04:42

What is the proper way of composing signals? Intuitively, I would like something like Monad in Haskell. @(compose-signal (subscribe {:a}) #(subscribe [:b %))

Quentin Le Guennec15:04:10

Something that works like >>= in Haskell

Quentin Le Guennec15:04:05

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

Quentin Le Guennec15:04:04

yes, something like that, but I think re-frame doesn't allow that, because a would need to be deferred?


Uhm. Why do you think that?


The code that I wrote is perfectly valid if used within a form-1 component.


Or within the render function of any other form.

Quentin Le Guennec15:04:10

oh nvm, you deferred it, it wasn't paying attention


Ah. Not "deferred". deref-ed. :)

Quentin Le Guennec15:04:26

Your code works indeed but it removes the potential for laziness. b cannot be computed if a is not deref-ed.

Quentin Le Guennec15:04:04

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.


And you must not call subscribe without ever deref-ing it. It creates a memory leak.

Quentin Le Guennec15:04:55

But I compose b with a just how I can compose two functions without having a value


So if you call subscribe, you must use the @-ed value at some point.


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.

Quentin Le Guennec15:04:35

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

Quentin Le Guennec15:04:09

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


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

Quentin Le Guennec15:04:41

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

Quentin Le Guennec15:04:53

No because re-frame doesn't allow it or because it's computationally impossible?

Quentin Le Guennec15:04:14

I could write the compose-signal function myself, right?


Do you know how Reagent reactions work?


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.

Quentin Le Guennec15:04:35

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.


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.

Quentin Le Guennec16:04:42

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

Quentin Le Guennec16:04:55

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.

Quentin Le Guennec16:04:51

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.

Quentin Le Guennec16:04:28

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.


What do you mean by increasing the data lag?

Quentin Le Guennec16:04:47

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.


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

Quentin Le Guennec16:04:56

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.

Quentin Le Guennec16:04:43

Not sure I am following you


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.


And if you do see or create such an example - again, please share if you can. :)

Quentin Le Guennec17:04:20

@U2FRKM4TW fyi, wrote this:

(defn chain-reaction [f r]
   (let [v @r]
     (if (and v (not (and (map? v) (:status v))))
       @(f v)

Quentin Le Guennec17:04:44

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?

Quentin Le Guennec18:04:43

yeah, (->> sig-a (chain-reaction f) (chain-reaction g)

Quentin Le Guennec18:04:00

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.

Quentin Le Guennec18:04:49

well let's say you have to chain queries, you could do somthing like this: (->> (subscribe [:a}) (chain-reaction #(subscribe [:b %})))

Quentin Le Guennec18:04:55

hate typing clojure anywhere else than in my editor err

Quentin Le Guennec18:04:21

(ofc the code I need to write depends on more than 2 signals)

Quentin Le Guennec18:04:55

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.


But if that works for you, then :thumbsup: :)

Quentin Le Guennec18:04:10

Not sure I'll end up using it, we'll see

Quentin Le Guennec18:04:47

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.

👍 2