Very cool. And probably useful for what I'm working on right now. 

Typo: "(defn on-changed...)" => "(on-change...)". Actually, I like the name 
"on-change" or "on-changes".

Cheers,

Jamie
On Apr 22, 2015, at 10:38 AM, Colin Yates <[email protected]> wrote:

> A cursive glance at this makes me want to marry you (if you don't mind
> me bringing my current wife and 4 kids?).
> 
> Thanks Mike.
> 
> On 22 April 2015 at 15:28, Mike Thompson <[email protected]> wrote:
>> On Wednesday, April 22, 2015 at 9:02:04 AM UTC+10, Mike Thompson wrote:
>>> On Tuesday, April 21, 2015 at 11:43:12 PM UTC+10, Colin Yates wrote:
>>>> Yes, that is a valid reduction. Specifically my register-handler,
>>>> which only has access to db needs to know the result of f.
>>>> 
>>>> The general principle of having my register-sub delegating to a defn
>>>> which is called from the register-handler is causing the pain because
>>>> there is actually a hierarchy of subscriptions going on here, so the
>>>> value of f might actually be resolving a chain of subscriptions. For a
>>>> simplified example:
>>>> 
>>>> (register-sub :reference-data/locations)
>>>> (register-sub :reference-data/active-locations .. (reaction (subscribe
>>>> [:reference-data/locations))))
>>>> (register-sub :page-1/location .. (reaction (if (following-defaults?
>>>> (-> db ...) @(subscribe [:reference-data/active-locations))))
>>>> 
>>>> the value of :page-1/location is of interest.
>>>> 
>>>> 
>>>> On 21 April 2015 at 14:27, Mike Thompson <[email protected]> wrote:
>>>>> On Tuesday, April 21, 2015 at 10:58:31 PM UTC+10, Colin Yates wrote:
>>>>>> Hi Mike - yeah, reading through I wasn't very clear. Let me try again
>>>>>> with a more fleshed out example:
>>>>>> 
>>>>>> On the server there is a hierarchy, each node in that hierarchy may
>>>>>> contain some meta data for example:
>>>>>> :id - the unique id of the node
>>>>>> :type - indicating some semantics about that particular node
>>>>>> :current? - indicating whether it is still actively used or not
>>>>>> 
>>>>>> Imagine there are 5 nodes and node 4, which is a leaf is not :current?.
>>>>>> 
>>>>>> On the client I have a number of projections of this data:
>>>>>> - an "active" tree which prunes out all nodes that aren't :current?,
>>>>>> so there are 4 nodes
>>>>>> - an "everything" tree which shows everything, e.g. all 5 nodes
>>>>>> 
>>>>>> In addition, every node in each tree is selected and there are
>>>>>> multiple instances of these trees.
>>>>>> 
>>>>>> On the client (on a reporting page for example, where one of these
>>>>>> trees are in the filter) I need to know which nodes the user has been
>>>>>> selected. I have a handler, which in response to some event (either
>>>>>> the server indicating new data is available or the user changing some
>>>>>> data) which needs to know which nodes have been selected.
>>>>>> 
>>>>>> If the user hasn't selected anything then the 'selected nodes' should
>>>>>> be the default set (i.e. all of the nodes). As soon as the user
>>>>>> changes the selection (by deselecting a node in the first instance)
>>>>>> the set of selected nodes is now every node that is selected (e.g.
>>>>>> every node apart from the one they just selected) and that instance of
>>>>>> the tree is no longer tracking the defaults.
>>>>>> 
>>>>>> The server is free to send new config data at any point in time. When
>>>>>> this happens, the default set should be updated. The non-default set
>>>>>> that the user has changed should also be consolidated as well, but
>>>>>> that is different.
>>>>>> 
>>>>>> Lets say my app state looks like:
>>>>>> 
>>>>>> {:page-1 {:some-active-tree {:selected-ids [] :tracking-default? true}
>>>>>>                :another-active-tree {:selected-ids [] :tracking-default? 
>>>>>> true}
>>>>>>                :some-all-tree {:selected-ids [] :tracking-default? true}}
>>>>>> 
>>>>>> The user hasn't done anything so the selected-ids should be the
>>>>>> default sets (4 ids for :some-active-tree and :another-active-tree and
>>>>>> 5 ids for :some-all-tree). If the user were now to deselect node 2 in
>>>>>> :another-active-tree then app-state looks like:
>>>>>> 
>>>>>> {:page-1 {:some-active-tree {:selected-ids [] :tracking-default? true}
>>>>>>                :another-active-tree {:selected-ids [0 1 3]
>>>>>> :tracking-default? false}
>>>>>>                :some-other-tree {:selected-ids [] :tracking-default? 
>>>>>> true}}
>>>>>> 
>>>>>> If they deselect node 3 in some-other-tree:
>>>>>> 
>>>>>> {:page-1 {:some-active-tree {:selected-ids [] :tracking-default? true}
>>>>>>                :another-active-tree {:selected-ids [0 1 3]
>>>>>> :tracking-default? false}
>>>>>>                :some-other-tree {:selected-ids [0 1 2 4]
>>>>>> :tracking-default? false}}
>>>>>> 
>>>>>> Should the server now update the config hierarchy changing node 4 back
>>>>>> to :current? and adding another node then at the very least the
>>>>>> 'selected nodes' for :some-active-tree should contain the ids of all 6
>>>>>> nodes.  :another-active-tree and :some-other-tree should also be
>>>>>> informed but they might not be updated depending upon the selections
>>>>>> (it gets more complicated...).
>>>>>> 
>>>>>> At this point it is clear that one solution is to record a delta from
>>>>>> the defaults, but that only works because we are talking about
>>>>>> booleans; there are other non-boolean use-cases unfortunately.
>>>>>> 
>>>>>> Another solution is to store the sets of defaults in app-state itself
>>>>>> rather than have it be a subscription and then overtime it changes
>>>>>> update the affected parts of app-state (:some-tree and
>>>>>> :some-other-tree in this example).
>>>>>> 
>>>>>> This would be much easier if it was just dealing with rendering data,
>>>>>> in which case subscriptions are a perfect fit, but the set of data
>>>>>> needs to be sent back to the server periodically in a handler, and
>>>>>> handlers can't see subscriptions.
>>>>>> 
>>>>>> To be frank, if anyone is still reading, my experience tells me that
>>>>>> if the problem is this hard to explain and requires this much
>>>>>> explanation then _I_ haven't understood it properly :), so I think I
>>>>>> need some more hammock time is in order.
>>>>>> 
>>>>>> Thanks for anybody who hasn't lost the will to live yet ... :).
>>>>>> 
>>>>>> On 21 April 2015 at 13:08, Mike Thompson <[email protected]> 
>>>>>> wrote:
>>>>>>> On Tuesday, April 21, 2015 at 4:52:05 AM UTC+10, Colin Yates wrote:
>>>>>>>> Hi,
>>>>>>>> 
>>>>>>>> This is somewhat reframe specific, but how do people handle 
>>>>>>>> default-values that can change? My specific use-case is that I have a 
>>>>>>>> tree which can be expanded and collapsed. By default the tree should 
>>>>>>>> be expanded to a certain level, however, as soon as the user manually 
>>>>>>>> expands or collapses a node they should no longer follow the default. 
>>>>>>>> The data the tree is displaying can change, meaning the defaults can 
>>>>>>>> change over time.
>>>>>>>> 
>>>>>>>> I can't simply define the defaults on startup because the defaults 
>>>>>>>> will change over time.
>>>>>>>> 
>>>>>>>> It can't be a subscription because the values need to be available in 
>>>>>>>> the handler.
>>>>>>>> 
>>>>>>>> My current thinking is that the app-db state has a 'follow-defaults?' 
>>>>>>>> which is true by default but is set to false when the user explicitly 
>>>>>>>> changes the state (e.g. by expanding or collapsing). When the 
>>>>>>>> underlying hierarchy changes from the server, propagate that change to 
>>>>>>>> all of the parts of the app-state that are interested.
>>>>>>>> 
>>>>>>>> To be explicit, imagine I have the following template for tree: 
>>>>>>>> {:expanded-ids [] :follow-defaults? true}. There are 6 instances of 
>>>>>>>> this template in the app-db  (i.e. 6 distinct UI trees). When the 
>>>>>>>> server informs the client that the source-data has changed it then 
>>>>>>>> updates each instance where follow-defaults?.
>>>>>>>> 
>>>>>>>> I understand the rationale as to why subscriptions can't be in the 
>>>>>>>> handlers but a subscription which switches on follow-defaults? seems 
>>>>>>>> ideal.
>>>>>>>> 
>>>>>>>> Maybe I could hack a UI-less component which reacts to that 
>>>>>>>> subscription change by directly updating the underlying db...
>>>>>>>> 
>>>>>>>> What would you all do?
>>>>>>> 
>>>>>>> 
>>>>>>> Hi Colin,
>>>>>>> 
>>>>>>> I'm not sure I'm clear on the problem. Here's my attempt to explain 
>>>>>>> back ...
>>>>>>> 
>>>>>>> You have some setting (data) in your app (tree display state) which can 
>>>>>>> be:
>>>>>>>   1. in a server-supplied state (you call this "default" state?)
>>>>>>>   2. optionally, in user-supplied state (if present, overrides 1).
>>>>>>> 
>>>>>>> Over time, new versions of 1. arrive.  If 2. exists, it always 
>>>>>>> overrides 1.
>>>>>>> 
>>>>>>> Have I understood?
>>>>>>> 
>>>>>>> --
>>>>>>> Mike
>>>>> 
>>>>> 
>>>>> Hmm.  I don't get the specifics, but maybe we can talk abstractly ...
>>>>> 
>>>>> You have values a and b which can change over time. And you have another 
>>>>> value c which is a function (f) of a and b.
>>>>> 
>>>>>     (f a b) => c
>>>>> 
>>>>> And, a b and c are all stored in `app-db`.
>>>>> 
>>>>> And then when, say, 'a' gets updated (server?  user?), you want to 
>>>>> (reactively) modify the value in c again.  After all, its value is a 
>>>>> function of a and b, and a just changed?
>>>>> 
>>>>> And you are reaching for `subscribe` as a way to sorta know when to rerun 
>>>>> 'f' to recalculate new 'c'.
>>>>> 
>>>>> Have I got it now?
>>>>> 
>>>>> --
>>>>> Mike
>>> 
>>> 
>>> 
>>> We have a similar situation with showing errors or warnings, because 
>>> showing them or not tends to be a function of multiple pieces of other 
>>> information in `app-db`.
>>> 
>>> If 'this', but 'that' in set 'so-and-so', then show a warning saying "You 
>>> have duplicates on X".  And the values in 'this' 'that' and 'so-and-so' 
>>> change over time.
>>> 
>>> Going back to my abstract version of this involving 'a' 'b' 'c', our 
>>> situation is that 'c' is some set of warnings which should be displayed to 
>>> the user and 'a' & 'b' are the state which must be  analyzed in order to 
>>> figure out if we have warnings.
>>> 
>>>   (f a b) -> c
>>> 
>>> So 'f' is some calculation on 'a' and 'b' to determine the value of our 
>>> warning value 'c'.
>>> 
>>> Our way of doing this is to run 'f' after every change.  We use the 
>>> 'enrich' middleware and we put it on every handler which could effect the 
>>> values of 'a' or 'b'  (we actually put it on every handler, just to be 
>>> sure).
>>> 
>>> So we don't even try to detect that 'a' or 'b' has changed. We just 
>>> recalculate 'c' every single time.  And we do it so that if the new 'c' 
>>> tests equal to the old 'c', it doesn't get assoc-ed into `app-db`.
>>> 
>>> This approach collapses the problem to being almost trivial. BUT it comes 
>>> at the cost of re-running 'f' re-compuation each time anything changes (via 
>>> enrich).
>>> 
>>> In our case, it has beautiful reduced a pretty tricky bit of dependency 
>>> updating.
>> 
>> 
>> Hmm. The penny has just dropped for me. A kind of "reverse reaction" is 
>> possible via middleware.
>> 
>> https://gist.github.com/mike-thompson-day8/76812d5452747bc79aac
>> 
>> Seems so right.
>> 
>> --
>> Mike
>> 
>> 
>> --
>> Note that posts from new members are moderated - please be patient with your 
>> first post.
>> ---
>> You received this message because you are subscribed to the Google Groups 
>> "ClojureScript" group.
>> To unsubscribe from this group and stop receiving emails from it, send an 
>> email to [email protected].
>> To post to this group, send email to [email protected].
>> Visit this group at http://groups.google.com/group/clojurescript.
> 
> -- 
> Note that posts from new members are moderated - please be patient with your 
> first post.
> --- 
> You received this message because you are subscribed to the Google Groups 
> "ClojureScript" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to [email protected].
> To post to this group, send email to [email protected].
> Visit this group at http://groups.google.com/group/clojurescript.

-- 
Note that posts from new members are moderated - please be patient with your 
first post.
--- 
You received this message because you are subscribed to the Google Groups 
"ClojureScript" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/clojurescript.

Reply via email to