Confluence of threads between this one and the question about subscriptions
just made me realize that passing the data model down through the view
model hierarchy on each update to the data model is not the worst thing in
the world from a performance standpoint when you consider that a
subscription-based approach would be collecting up subscriptions from all
of the parts of the view model hierarchy and that would seem like a similar
amount of work unless one cached a lot of the subscriptions in the view
model for faster return.

So, that's good. On the other hand, another thing that subscriptions are
good for that this doesn't answer is as a way of expressing interest.
Imagine a data model with a list of chats and for each chat we have a list
of messages:

chats : DataModel -> Dict ChatID String -- chat id's and names

messages : DataModel -> ChatID -> List Message

We can pass the full data model around. The chat list view can extract the
list of chats. A message view for a particular chat can extract the list of
messages.

For efficiency reasons, we may only want to stay synchronized to the cloud
on the chat list and the current chat. Knowing which chat is current,
however, is really a view-side property. For example, the above API would
be just as friendly to having views into two chats at once. We could have
data model operations to start and stop following a chat. Starting is easy.
We just send the appropriate out message when we construct the view.
Stopping, however, is harder. One of the nice things about subscriptions is
that they go away when the piece of the model doing the subscribing goes
away. The functions we probably really want are something like:

getActiveChats : ViewModel -> Set ChatID

setActiveChats : Set ChatID -> DataModel -> DataModel


We need to call the former routine whenever the view model changes and use
the results to update the data model. Since that will change the data
model, we should then pass the updated data model to the view model causing
an update to the view model thereby looping us back around. To stop the
loop, we need to recognize when something hasn't changed and break the
cycle. That's easy enough but fragile since it would use an equality test
and equality isn't safe to use on arbitrary types. Effects managers don't
have this problem because we don't update the effects manager based on the
main model but rather the effects manager sends messages back to the main
model.

(I'm thinking about this a lot right now because I'm trying to move code to
an API where we need to request rendition URLs based on asset IDs over a
web socket where we are maintaining a synchronized set of assets of
interest between the cloud and the client. Coming up with a clean way to
organize the code is resulting in lots of exploratory sketches.)

Mark

On Fri, Jul 21, 2017 at 9:43 AM, Mark Hamburg <mhamburg...@gmail.com> wrote:

> One argument against the "pass the model to all view functions" approach
> is that it pretty much blows up laziness.
>
> If one ignores that, then one could do something like the following:
>
> * All view-side functions including both update and view receive a global
> context containing the data model. (First argument? Last argument? I'm not
> just throwing this out here. I'm looking for feedback on conventions
> because I've had other places where a global context parameter has come
> up.) This can be in addition to view-side model data as well such as which
> element has focus.
>
> * Commands get replaced on the view side with a construct that can embody
> both operations on the outside world (i.e., traditional Elm commands) and
> operations on the data model. The view side doesn't get write access to the
> data model. The data model is modified in an update fold just as is normal
> for Elm models.
>
> The downsides to this approach include:
>
> * It obscures the true dependencies between the data model and the views.
> The compiler will catch changes (assuming they affect type signatures) but
> the codebase may still feel hard to reason about. That said, one could
> always provide multiple modules for interpreting the data model and one
> could look at the dependices on those modules to reason about the
> relationship of the views to the data model. In other words, it seems icky
> but it might not be in practice.
>
> * The view code can't react to changes in the data model — or at least
> can't do so cheaply. This matters because the view code may want to do
> things like change what it is focused on if an item in the data model goes
> away. Or in the case of the code I'm working on, we have some expensive
> layout logic that depends on he width of the view and the list of items
> displayed in the view. We don't want to run this logic every time we render
> so we need to know when either the width (a view-side property) or the item
> list (a data-model property) changes. The non-cheap solution here is
> broadcasting the new data model (or the old and the new data model) through
> the view hierarchy on roughly every data model update.
>
> Another solution would be to manage the data model via an effects manager
> thereby allowing it to expose both commands for changes and subscriptions
> for updates. This seems ideal for the scenario you've presented. The
> problem is that effects managers cannot, AFAIK — please correct me if I'm
> wrong, use other effects managers, so taking this approach cuts your data
> model code off from the standard web sockets and HTTP implementations.
>
> To get any deeper into Elm-based approaches, I think we would need to know
> more about Re-Frame so any further details you can supply or point to would
> be welcome.
>
> Mark
>
> On Fri, Jul 21, 2017 at 8:29 AM Martin Norbäck Olivers <norb...@gmail.com>
> wrote:
>
>> Hi!
>> We discussed this on the slack the other day, right?
>> Let me just get this straight, the Re-Frame solution depends on functions
>> having access to a global "db" object or similar (that corresponds to the
>> Elm model)?
>>
>> How is that conceptually different than passing the model as a parameter
>> through the functions? Any function can access any part of the state by
>> subscribing in Re-Frame, just as it can access any part of the state by
>> accessing the passed-through model in Elm.
>>
>> I just want to know what more this gives than avoiding the boilerplate of
>> passing the model through.
>> Btw, Elm has pure functions so implementing this straight-off is not
>> possible.
>>
>> Regards,
>>
>> Martin
>>
>> Den fredag 21 juli 2017 kl. 15:55:42 UTC+2 skrev ajs:
>>>
>>> I had an interesting discussion with several members on the #beginners
>>> channel on Slack. It was suggested I post this out to the larger community
>>> for input.
>>>
>>> As a quick background, I'm a professional Clojurescript developer for
>>> many years, and have been through the early days of React (when it was just
>>> wrapped as Om in Clojurescript), and then Reagent, and then the most
>>> versatile and structured tool, Re-Frame, which has emerged as a leading
>>> model of UI <-> Model interaction. I am now looking seriously at Elm on my
>>> company's behalf and we are testing a prototype for a component in it.
>>>
>>> The Elm Architecture is often compared to Redux and Re-Frame. The
>>> overall flow is similar, however there is a particular problem that plagued
>>> Clojurescript's Om in the early days that was eventually worked out in
>>> Re-Frame and in later versions of Om. This problem, however, does not
>>> appear to have a solution in Elm, and I wish to outline it here. It is a
>>> common problem that occurs in modelling a UI in a complex SPA, anything
>>> truly non-trivial.
>>>
>>> The way the Elm Architecture works, and in the early Om, is that you
>>> have a single piece of state that acts as the truth for your app, and then
>>> you have view functions that receive parts of this state (or all of it),
>>> and then they call children view functions and pass along parts of the
>>> state they received. I have 2 Elm examples below, in a moment.
>>>
>>> The fundamental problem with this approach is that it creates a tight
>>> coupling between the organization of your model data and that of your view
>>> hierarachy. What the Om users realized is that a robust model should not
>>> depend on the views for its structure. But the Elm Architecture kind of
>>> requires that (as far as I can tell). Data models should not be structured
>>> based on what works for a user interface; they need to honor the
>>> requirements of the data, only.
>>>
>>> A very large and prominent Om-based user-interface is that which
>>> CircleCI uses. They wrote about this issue here:
>>>
>>> (skip to the heading "The Conundrum Of Cursors: Most Data Is Not A
>>> Tree.")
>>>  https://circleci.com/blog/why-we-use-om-and-why-were-
>>> excited-for-om-next/
>>>
>>> If the model does not fit the needs of the view hierarchy, then what
>>> emerged in the old Om was the unfortunate side effect of passing the entire
>>> model state through all view functions. I hear that this is not uncommon in
>>> Elm as well.
>>>
>>> It has plagued many companies (including mine), which eventually led to
>>> a much better way of data interacting with a UI in Re-Frame, which I will
>>> summarize in a moment.
>>>
>>> The problem arises when a view child needs data in the central model
>>> that its parent did not need or receive. The parents and ancestors should
>>> not require awareness of specific model details that a distant child might
>>> need. For example, a view function might have the job of displaying Screen
>>> A or Screen B. It is passed a flag, perhaps, that tells it which screen to
>>> build, and then it calls a view function for that screen. Simple as that;
>>> it doesn't need to know that Screen B has a widget that contains a dropdown
>>> menu that must show a list of items from somewhere in the central model,
>>> and that it must also find and pass that list to the screen view function.
>>>
>>> The other problem that arises is when a child requires access to more
>>> than one part of the central model, and those parts are not "in the same
>>> place".
>>>
>>> One "solution": you pass the entire model state to all view functions,
>>> then each view just takes what it needs. But this is very poor for a
>>> variety of reasons, not the least of which is that children now have much
>>> more access than they need. And it can lead to more code as each child has
>>> to go grab what it needs from a large monolithic data source before
>>> operating it -- it requires children to be responsible for both the query
>>> and the processing. It makes it hard to trace data access through your app
>>> when all views access the same global model. And it requires more code on
>>> behalf of children.
>>>
>>> Here are two Elm examples that show the problem:
>>>
>>> Line 22 on:
>>> https://ellie-app.com/3JbGH7v2v7ra1/1
>>> How to give the child data that its parent didn't need or receive? Here,
>>> I've hard-coded it to 0 because there is no other obvious way to access
>>> what the child needs.
>>>
>>> Another attempt:
>>> https://ellie-app.com/3JbNQk26qNRa1/0
>>> On Line 32, an ancestor is still having to hunt down data that a
>>> potentially very distant child would need, and actually handle the building
>>> of that child.
>>>
>>> We have an app with hundreds, perhaps over a thousand, independent view
>>> components, in a hierarchy about a hundred levels deep. This relationship
>>> between data and components breaks down at that scale (and actually at
>>> scales far smaller than ours) -- unless you can provide me with a technique
>>> I haven't considered. It either leads to a lot of spaghetti code where
>>> things have access to data they shouldn't and where components are handling
>>> lots of intermediate data they don't directly use, or, it leads to
>>> widespread use of global data for even the tiniest of detailed views.
>>>
>>> This problem was solved in the Clojurescript community in two ways. I
>>> won't discuss the Om Next method because it is still in alpha and not
>>> widely adopted yet. Re-Frame, however, is extremely popular, and this is a
>>> summary of how it works:
>>>
>>> Just as you have a single central data store, you also have pure
>>> functions that stand alone as queries to the data store. A query can be as
>>> simple as pulling out a record value, or a deeply nested value, or the
>>> query function might actually do some prep or calculation on required data
>>> and return that. Doesn't matter, they just control the data flow to the
>>> views. These functions are called "subscriptions". Each view function can
>>> subscribe to any of these functions. This has at least 3 outstanding
>>> benefits: 1) each child is truly a separate component and does not need to
>>> be aware of what it's own children need, and doesn't impose dependencies on
>>> what its parent needs to send it, and also 2) it places all read access to
>>> the db in one place (well, assuming you put all these subscription
>>> functions in the same file, which is common), and 3) it gives a children
>>> razor-sharp focus on just exactly the data it needs to build itself, and
>>> nothing more. You always know how the data is getting accessed by your
>>> views without actually looking at your view code. It's not unlike how in
>>> Elm you always know what possible Messages are getting sent, because they
>>> all eventually collapse to one entry point. That's nice. I'd like to know
>>> how to do that in Elm for reading data too, because it's very useful.
>>>
>>> I should note that the way Re-Frame handles updates, or state-changing
>>> messages, is essentially the same as Elm.
>>>
>>> So it all boils down to the data a view needs to build itself, and how
>>> to remove this burden on the view's ancestors.
>>>
>>> I would really like some ideas/examples how to approach this problem in
>>> Elm, if there is a clear solution.
>>>
>>> Cheers,
>>> Andrew
>>>
>>>
>>> --
>> You received this message because you are subscribed to the Google Groups
>> "Elm Discuss" group.
>> To unsubscribe from this group and stop receiving emails from it, send an
>> email to elm-discuss+unsubscr...@googlegroups.com.
>> For more options, visit https://groups.google.com/d/optout.
>>
>

-- 
You received this message because you are subscribed to the Google Groups "Elm 
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to elm-discuss+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to