Here is where our app is migrating after starting as an intermixture of
view-side code and data-side code.

The high level split is a store and a display. The store knows about things
like how to talk to our server. As I summarized it, the UX code should know
nothing about Phoenix. The display manages the user interaction. Both sides
have state. The store, well, stores information — generally a reflection of
what is on the server or should be on the server. The display, however,
also stores information like whether panels are open, the state of
animations, etc. Just as knowledge of Phoenix doesn't belong in the UX,
knowledge of animations does not belong in the store.

This isn't a radical design by any means. A lot of code is built this way.
It's perhaps more interesting to this discussion to observe that the Elm
community doesn't encourage even this sort of factoring and in fact naive
use of MUV/TEA does lead to a deep intermingling of store and display
concerns. (I guess the "just write one monster model" approach deals with
this by saying that code can choose to only interact with parts of the
model.)

What I've been working through while engineering this split is how to
manage coordination between the two sides.

Sending messages from the display to the store is pretty simple. We've
replaced commands in the update signature with a list of "out messages".
These messages include internal notifications — e.g., canceled, finished,
please shift the target somewhere else, etc — and operations for the world
beyond the display which includes commands to flow out the top of the app,
requests for work by the store, and requests for random number generation
using a shared generator. (This last is much like the effects manager
random logic but it uses the Pcg generator.) The operations are all subject
to command style mapping to adjust the tagging for their responses back to
the display. Notifications are just notifications. The current
implementation involves more boilerplate at every nesting within the
display than I would really like but I'm working on that.

Getting data from the store to the display is more interesting and is an
area I continue to work through.

One naive approach we looked at would be to just hand the store into the
view functions and let them query the store as needed to do their work.
There are two problems with this.

The first is that some display side concepts such as which item is
currently targeted/selected — it's a display-side concept since the server
should never know — need to update in response to store-side changes. For
example, if the selected item goes away, we may want to automatically
select something else. Since we can't do updates during view calls, we need
to provide a way for the display to react to store changes. Our solution
here has been to treat this as one would treat the data if it were entirely
stored on the server: we store a local copy on the display side and perform
a fetch operation to get it from the store. This means that the display
gets to validate its copy and the problem of keeping that copy up to date
is exactly the same as the problem one would have if there were no store
and we were just talking over the wire to a server.(*)

This leads to the second problem: A fetch operation is great for getting
the initial information and replacing commands with operations makes that
pretty easy to implement but how do we keep the data up to date and
relatedly how do we keep track of when the display side loses interest?
Here I'm looking at building a subscription equivalent (an observation?)
but I'm not wild about what it would take to make it efficient in the
presence of say scrolling a grid of several thousand items starting and
stopping observations as the visible items change. But efficiency with
large sets of subscriptions seems like something the Elm implementation
hasn't really worried about either since I think it regenerates and
collects up subscriptions after every mutation — if you adhere to the "no
functions in the model" advice(**), you can't cache your subscriptions
because they hold tagger functions — and has the effects managers diff the
new subscriptions against the old. Maybe things are just efficient enough
as it is but I suspect that it's more likely that with so few effects
managers for Elm, few applications generate large number of subscriptions
at any one time. But lacking any better ideas, we will probably start with
mirroring subscriptions and see whether it blows up the time complexity in
a noticeable way.

Moving on, there's one other bit of code structuring that we've only
recently adopted but which has clarified a lot of code and should help with
writing unit tests. The display side models for the overall experience or
pieces of the experience or particular modal portions of the experience
(e.g, sign in) tend to have a model that hold display specific information
like the state of panels or animations and a logic submodel that holds all
of the business logic including figuring out whether the data is valid and
if not what errors to display. This logic submodel is also the part that
talks to the store thereby further keeping the display out of the loop
beyond needing to forward messages on behalf of the logic. Logic submodels
follow the standard model-update-view pattern but here view is replaced
with a toViewData function that returns the contents of the submodel in a
form designed for use in driving the display. These logic submodels have
also proven useful for code reuse because we've been able to build a couple
of different sign in experiences (needed for contextual reasons) while
using the same underlying logic that understands signing in or creating an
account.

It should be noted that while we apply the MUV/TEA paradigm through the
app, we only push it through where it makes sense. The app UX has a certain
structure and particular modalities. That results in just a couple of
layers of TEA nesting. The logic submodels result in one more level of
nesting. But within a major view modality or within the logic, the code can
get as ball-of-mud-like as seems appropriate. The abstraction barriers
exist not to make everything architecturally pristine but to keep one ball
from getting overly entangled with another ball and mean that if we want to
throw out a piece and replace it, it's boundaries are clear. Similarly, the
store basically presents a single API though we've observed that requests
can be structured in much the same way that the tagging works for message
delivery if we wanted to nest the code.

Finally, if writing monolithic applications and then adjusting them on an
ad hoc basis is the way one prefers to work, I don't want to stop you. I
just know that I've seen what that tends to do to codebases that last
multiple years and have developers coming and going or that have core
components one would like to reuse in new ways. (I've dealt with a C++
codebase where the central class had a huge number of public methods
because they were used by various internal parts of the overall initial use
for the codebase. When trying to turn this into a library for use
elsewhere, there was a big question of what the real external API was.) I
could argue about whether architecture is premature because code probably
won't endure or whether lack of architecture contributes to the premature
death of code, but people should generally know their use cases and which
things matter. You don't need pretty architecture for a shell script to get
a specific job done. What concerns me in the seeming push back against
architecture in Elm apps is that it may lead to decisions in Elm's future
that make it harder to build structured applications — e.g., if hierarchy
is discouraged, why keep Cmd.map? So, on the one hand, we have some of us
in the community asking, how best should we apply structure to an Elm app
believing that structure is a good thing for enduring codebases and on the
other hand simply saying "please don't make it harder to apply structure to
an Elm app".

Mark

(*) In my dream implementation, the store and the display could actually
run on separate threads. That's not going to happen in any browser
environment at the moment.)

(**) If the store can't respond to a request immediately, it is going to
have to hold onto the tagger function for its eventual reply. I'm not
sweating that though it means that testing for store equality is not an
option.

(***) Okay, that's a normative statement. When we started this, we were
pushing it deeper and we're now unwinding that as we unwind the
store/display entanglement.



On Wed, Apr 19, 2017 at 7:52 AM, Marek Fajkus <marek....@gmail.com> wrote:

> First of all I have to say I'm really glad we have this discussion. I know
> it's sometimes frustrated to face different opinions but outcome is
> definitely worth it. It looks like we're really not on same page and can
> learn something from other here. Also it seems that groups are good place
> to do that.
>
> I have quite some points to ongoing discussion but I really have no time
> to put them down now since I have to work on things with colleagues.
>
> Anyway since nobody reacted on @peter's comment and I feel it really needs
> to be addressed let me add my opinion just on that for now:
>
> How about the case where one has calls to a server involved? for example a
>> weather widget or some kind of "Quote of the day" widget, a stocks ticker,
>> etc.
>> How does one handles side-effects (http, random, time etc) with this
>> pattern?
>
>
> Creating "components" that  do any kind of HTTP is probably way to hell.
> The only exception are really simple things like let's say jQuery plugins
> or anything which is really really really super isolated.
>
> Generally networking should be on real surface of whole system. There are
> many things that can go wrong. This is the ultimate side-effect since one
> system talking to another over "Maybe Connection" doesn't have any
> guarantees about what that other system does. And you have to assume the
> other system will fail in one way or another. Dealing with failure is
> difficult itself and is hard to do in isolation of system since it pretty
> often has some impact of other things. *Api is the only real "mutable
> object" you have in TEA* (and you can write it in Haskell and do not
> change anything about this fundamental truth)*.* Api is actually global
> (your domain) mutable (even hello word "pure" can return error 500)
> singleton. People with some Erlang experience know costs of fault
> tolerance. I remember in some Evan's comment in Elm's either stdlib or
> compiler there was some note about OTPish (Open Telecom Platform) message
> passing. Anyway even thought Actor model is really great interesting model
> for concurrency it's crucial to understand it leads to some inherited
> complexity. We surly don't want to implement actors in Elm. Rich Hickey
> (author of Clojure) would probably said something like "Adding networking
> stuff to component is completing concerns of how to work with A and how and
> where to get things A needs".
>
> Also getting data is usually not the only thing you need to think of. Say
> you have Blog with posts and you're building admin. There is list of posts
> on left and post detail on right. Now you want to add "delete" function to
> post list so you can click on "delete" button and delete post. The right
> way to do it is emit some action from UI on click (This is what Html.Events
> do for you) and handle it on some very top/surface level - usually update
> but in case of composed updates (like "tea-component") on* the very top
> one*. Why? Because in actions like this you need to really look at system
> as whole. What should happen when user is trying to delete post which is
> actually opened? How can I tell if it's open (probably from route) and so
> on. Only on most top level you can control whole system (even though you
> might use higher lever interfaces of self contain units that if you have
> them).
>
> Of course there are exception but I would rather avoid examples of
> "component that can do http" since this is really where people can hurt
> themselves.
>
> Actually this is precisely why "tea-component" was using that "Action
> Bubbling" and and sending messages to upper component. Component might
> knows how to propagate "delete" action but has no idea about how to proceed
> delete. That is really concern for system as a whole. This is sort of same
> as Html.Events just on higher level. Child just asks "Hey this just
> happened and you might be interested" and system as whole knows how, when
> or if at all to do with it.
>
> This is at least what I thing based on my experience. Hope it makes sense.
>
> On Wednesday, April 19, 2017 at 6:56:23 AM UTC+2, Peter Damoc wrote:
>>
>> On Wed, Apr 19, 2017 at 2:58 AM, Richard Feldman <richard....@gmail.com>
>> wrote:
>>>
>>> "Everything ought to have the same API" is a much harder claim to
>>> defend. It sounds wrong at face value, and I haven't seen any evidence (in
>>> this thread or elsewhere) to convince me that it's a wise goal to pursue. :)
>>>
>>
>> But isn't the entirety of the html package and actual example of an
>> unified API?
>> All the widgets there have the same API: widget : List (Attribute msg)
>> -> List (Html msg) -> Html msg
>>
>>
>> checkbox : (Bool -> msg) -> Bool -> Html msg
>>
>>
>>
>> How about the case where one has calls to a server involved? for example
>> a weather widget or some kind of "Quote of the day" widget, a stocks
>> ticker, etc.
>> How does one handles side-effects (http, random, time etc) with this
>> pattern?
>>
>>
>>
>>
>>
>>
>> --
>> There is NO FATE, we are the creators.
>> blog: http://damoc.ro/
>>
> --
> 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