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.