Hacker Newsnew | past | comments | ask | show | jobs | submit | kodebach's commentslogin

Do you have an example of where "the ideas don't combine well"? Or where an imperative approach is truly better?

In my experience the solution is often to use the appropriate architecture underneath your UI layer. Basically you build a data structure that represents your UI and always hand this entire structure to the declarative UI layer. Underneath the UI layer, you can still use imperative code to manipulate the data structures. The benefit of the reactive/declarative approach is that you don't have to think about how changes in the data need to be reflected in the UI. That's the framework's job.

Note: With "data structure" I don't mean "build a shadow DOM". I mean something specialized to your use case.


One example of where I think imperative generally does better is UI that requires substantial setup or customization.

In SwiftUI, that means endless chains of modifiers, and in Compose this means monstrous constructors and modifier chains. Both get really ugly and painful to read quickly, and there's not a lot that can be done about it apart from breaking everything out into smaller views (which only goes so far). The amount of boilerplate saved isn't worth the trade, in my opinion.

In an imperative setup, there's still a lot of code but there are more options for organization, readability, and overall clarity. One can break things up with comments, break out setup into functions with self-explanatory names (that the IDE can then quick jump to, as a bonus), etc.

Yes, it's easier to get tripped up with imperative frameworks if one isn't thoughtful with managing their data, but much of the time that code only needs to be gotten right once.


>in Compose this means monstrous constructors and modifier chains.

Which is now being fixed by the Styles API (https://developer.android.com/develop/ui/compose/styles) which are easily reusable.


The goal of the reactive/declarative approach was never to be more performant than imperative code. The goal is to more easily build UI that is performant enough and functions correctly. With imperative UI code it is incredibly easy to forget an edge case in your update logic.


Not if you actually do MVC, so solved around 50 years ago.

1. The UI tells the model to change.

2. The model does the change and possible related changes.

3. The model notifies the UI that something has changed.

4. The UI updates itself from the model.

Alas almost nobody does MVC, despite calling what they do MVC.


MVC is not bad, but it is not a silver bullet. Calling MVC an ultimate solution to UI is oversimplification. Just looking at the steps you listed I can ask:

How do you collect all notifications on step 2 to fire them on step 3 such that UI does not re-render itself too much? E.g. updating a title of each item in a list of 100 items should not trigger 100 renders. Or 100 layout calculations (which I think is harder to avoid).

How do you deal with situations where on step 4 UI triggers an event that your model happens to listen and the cycle repeats while killing performance?

Because you rely on events how do you avoid “event hell”? That is, a situation when an event handler triggers a change that triggers another event handler that triggers a change and so on. Sometimes it is scrolling or typing, sometimes it is parts of the model subscribed to each other bubbling events to UI.


I never claimed MVC is a silver bullet. Just that it solves "... incredibly easy to forget an edge case in your update logic."

> UI does not re-render itself too much?

Glad you asked! In my Blackbird reference architecture (which is an instance of MVC), I use a coalescing queue to capture the updates. The coalescing is two-level: first, simple duplicates are weeded out. Second, if the update queue gets very full, it becomes coarser-grained, and weeds out duplicates based on that coarser grain. This has multiple steps of grain up to "just re-render the whole UI". Worked like magic in Wunderlist. Except it wasn't magic at all and very simple, inspectable and tractable.

> step 4 UI triggers an event that your model happens to listen

That's not allowed in MVC.

> Because you rely on events how do you avoid “event hell”?

I don't "rely" on events and there is no "event hell". Events are only used in the M→V communication part and there are no subsequent triggers, because the only event is "the model has changed", with an optional payload specifying which part of the model. Important: it must not contain the data that changed, this the view has to fetch from the model once it processes the update event.

Since the only event used is "the model changed", the view cannot ever be a source of those events, so no "event hell".


How do you handle UI state vs. underlying data (model) state, and dependencies between them? By UI state, I mean things like scrollbar position and selection state. When displaying a scrollable and selectable list of items, then for example when the number of items changes, the selection may need to adjust, and the scroll position may need to adjust. Depending on which items are added or removed (or reordered), the selection and scroll position may need to change differently for the apparent UI state to look stable for the user. If only the model is changed, a previous UI state like selection or scroll position may become invalid in relation to the new model state. Who updates the UI state accordingly to make it valid again? In the general case, application code needs to be involved in choosing the desired valid UI state when the underlying model state changes. How is the corresponding application code prevented from triggering further events?


>How do you handle UI state vs. underlying data (model) state, and dependencies between them?

I don't. And I don't have to, as I delegate that sort of stuff (mostly) to Cocoa/CocoaTouch etc.

https://blog.metaobject.com/2018/12/uis-are-not-pure-functio...

When you have stateful view objects, these stateful view objects maintain the view state. When updating themselves with new data due to a ModelDidChange notification, they take care of reconciling their current display state with the underlying model state.

> When displaying a scrollable and selectable list of items

So for example an NSTableView or NSCollectionView. I personally use a subclass that interacts directly with a table representation, meaning a lot of the glue code that Cocoa(Touch) requires disappears.

> Who updates the UI state accordingly to make it valid again?

Always the view. Who else?

> In the general case, application code needs to be involved in choosing the desired valid UI state when the underlying model state changes.

How so? The view is always a reflection of the model data. Whether that is a "change" is actually mostly irrelevant, even though the notification is called ModelDidChange in my case. In Smalltalk MVC it is the #changed message. It means "you are out of date, please make yourself reflect the model".

This same mechanism also handles the model being changed by some other party without any further code. "The model has changed, please update yourself to reflect the current state of the model". That's it, modulo optimizations.

> How is the corresponding application code prevented from triggering further events?

Model code isn't involved. A ModelDidChange event is only triggered when...er...the model changes.

That said nothing prevents you from manually invoking the ModelDidChange notification, just like nothing prevents you from calling abort(), running an infinite loop, creating an unbounded recursion or reading from /dev/random until it is exhausted ...

Doing it by accident, though, is very hard, because it just isn't part of the programming model.


>The coalescing is two-level: first, simple duplicates are weeded out. Second, if the update queue gets very full, it becomes coarser-grained, and weeds out duplicates based on that coarser grain

This is not about duplicates. For example, sync updates 100 items in a list changing their titles. Items are bound to a list in the UI. Thus, 100 unique title update events triggered.

>Events are only used in the M→V communication

I don’t understand. Button clicked -> model change -> view update -> new event triggered -> model or view updated again … This is not something one would code on purpose, but often an attempt to create relationships between view. Like a custom layout code. Might not include model at all, just views being updated in an event handler trigger more events and more updates to views.


> For example, sync updates 100 items in a list changing their titles. Items are bound to a list in the UI. Thus, 100 unique title update events triggered.

Those "updates" go in the queue. When the UI gets around to updating itself, it looks at the queue and invalidates all the UI elements that refer to the model items in the queue.

It then updates those elements, using the coarsening to update larger elements in bulk if that becomes better.

> Button clicked -> model change -> view update -> new event triggered -> model or view updated again

Once again, that is not allowed. View updates are not allowed to trigger any events in MVC. A model → view update updates the view. That's it.

The only event is "model changed", so it also doesn't make sense for the view to generate those events.


I can only say how I did this in the Azul GUI framework[1] (note: not production ready yet), which may be close to what you're describing. So in Azul, you do this:

  class DataModel:
    def __init__(self, counter):
        self.counter = counter

  def layout(data, info):
    return Dom.create_div()
             .with_child(Dom.create_text(str(data.counter)))
             .with_css("font-size: 32px;")

  def on_click(data, info):
    data.counter += 1
    return Update.RefreshDom

  model = DataModel(5)
  window = WindowCreateOptions.create(layout)
  app = App.create(model, AppConfig.create())
  app.run(window)
So, there's no "automatic" re-render, a callback has to return "Update.RefreshDom" or "Update.DoNothing" (default).

Now to your questions:

> How do you collect all notifications on step 2 to fire them on step 3 such that UI does not re-render itself too much?

Diffing, and then caching very aggressively. The click causes the model to re-call the layout() fn to return the entire DOM, however, there are ways to make this step very fast (arena allocation / no allocation). Then this gets diffed with the previous DOM state and the framework internally reuses everything it can (with user providing keys for list items, like React does).

> How do you deal with situations where on step 4 UI triggers an event that your model happens to listen and the cycle repeats while killing performance?

Azul has a "max recursion depth" of 5 and then just throws an error (infinite cycle). So, it will invoke all the relevant callbacks for a frame, then "sum up" all of the Update enums (i.e. one callback returned RefreshDom -> now we need to repaint).

> Sometimes it is scrolling or typing, sometimes it is parts of the model subscribed to each other bubbling events to UI.

Scrolling, selection, typing, etc. are handled by the framework. To make something editable, you need to set "contenteditable=true" on the Dom node (like on the web). Then, on text editing (which can also come from IME, a11y input, copy-paste), you get a "text changeset". The callback can then "reject" the changeset or allow it (default, since you already set contenteditable before).

Azul has a "dual update pattern" for performance here, i.e. the DOM itself is immutable until the next layout() call, however for "quick edits" like dragging a node you obviously don't want to call layout() again and construct an entire new DOM tree. So there, you just (conceptually, don't know the current API for this):

  def on_div_dragged(data, info):
    mouse = info.get_window_state().mouse_state
    info.set_css_property(info.get_hit_node(), "transform: translate(%s, %s)", mouse_state.x, mouse_state.y)
    # store in data model or node if necessary
    data.user_mouse_pos = mouse_state
    return Update.DoNothing # no re-render here
So, if another callback fires in between, the data model is still properly up to date. Azul also aggressively reconciles focus, scroll position, selection, text cursor position, etc. But Azul does not allow "one event auto-triggers another" like SolidJS does, it looks nice on a slide deck and then is a pain to debug Rube-Goldberg state machines.

This also works for text input or updating images (i.e. you don't need to call layout again on text input). Update.RefreshDom is for "larger / structural" changes, i.e. something like a route switch in a SPA-style app. Azul tracks the text cursor position by diffing the actual text, so the user code doesn't have to track the text cursor and state is preserved during a diff (it can also retain heavy elements).

For large lists, there is a native "virtualized view" DOM node with a callback that is being called "during" layout (after the size of the container has been determined, then the framework asks you to render your DOM, given the scroll position). So, that can be diffed, too. You never render in the DOM more than ends up on screen, so the perf is manageable.

Scrolling and retaining scroll positions inside a virtualized view is still an ongoing topic (not impossible, you just have to have functions to measure the DOM items before you return them, to estimate how much you need to render, and then do the math for "where are we right now, where is the scrollbar, how big is the virtualized view in relation to what we're rendering" - so the framework can set the right scrollbar size and position).

Again: please don't use or post Azul here on HN yet, docs are still slop and undergoing review, API is unstable until I have some apps going, but I just wanted to answer these questions.

[1] https://azul.rs/ui/


I see an M and a V in this description, but no C.

Is the UI updating itself automatic or manual? Because if it’s manual, that’s precisely the error-prone part that you’re saying this approach somehow solves - you’ve done the “How to Draw an Owl” meme. If it’s automatic, that doesn’t seem especially different from the React/Redux/Elm/SwiftUI approach (as a sibling points out).


Yeah, M-V-C are all roles, not concrete objects. The C mediates between the input devices and the model, but in practice views can and often do fulfill that role as well. Cocoa views, for example, also fulfill the C role.

Different formulations of M-V-C have the C deal with more complex interactions, with sequences of interactive prompts like wizards.

The update is essentially automatic, and yes: MVC already solved the "problem with MVC" React/Redux/Elm/SwiftUI claim to solve. In 1979.


I like my Controller to be responsible for all the "business logic" so that its all in one place. It's the important part. The View layer is always fairly verbose and full of fluff. Especially if you have a lot of animation and formatting type code.


> I like my Controller to be responsible for all the "business logic" so that its all in one place.

Business logic is supposed to go in the model. All of it. Because it's the important part.

Controller these days can be largely empty.

"MODELS Models represent knowledge. A model could be a single object (rather uninteresting), or it could be some structure of objects.

There should be a one-to-one correspondence between the model and its parts on the one hand, and the represented world as perceived by the owner of the model on the other hand. The nodes of a model should therefore represent an identifiable part of the problem.

The nodes of a model should all be on the same problem level, it is confusing and considered bad form to mix problem-oriented nodes (e.g. calendar appointments) with implementation details (e.g. paragraphs)."

https://web.archive.org/web/20090424042645/http://heim.ifi.u...


Are you saying the UI always updates its entire self whenever anything changes in the model?


Yes and no.

Conceptually, the UI re-renders itself completely in order to always be an accurate reflection of the model.

That is the #1 job of the view: be an accurate reflection of the model.

And re-rendering itself completely is a safe way to implement that requirement.

However, the UI can also look at the model in more detail and figure out what parts need to change, as long as the effect is the same as re-rendering everything.

And the model can tell the view that specific subparts of the model have changed to make that job easier for the view.

But if it can't figure out the details, the fallback is to re-render the entire view from the model. But not to recreate the view. The view sticks around.

One way of doing this optimization is "damage rects" like Cocoa does. Another are the polymorphic identifiers used in the update queue of Blackbird.


Immediate mode UI ftw.


Funny, this almost reads like a description of how Elm works.


Yep, to anybody who actually knows MVC, the whole Elm/React/SwiftUI stuff is funny.

"We solved the problems of MVC by properly applying MVC".

https://blog.metaobject.com/2017/03/concept-shadowing-and-ca...


Dude, you didn't describe MVC at all, but MMVC.

MVC, the controller is the intermediary between the services/data models, and the views. That still one of the best / simplest way to build large apps. MMVC is just a variation of it, with the models being able to communicate state to views and bypass controller if needed.

MVC, is still one of those 'fundemental as simple as it gets, and it gets the job done' patterns.


Dude, what I describe is exactly MVC.

Your interpretation is a common misconception, for example promulgated by Apple. It is not MVC.

https://blog.metaobject.com/2015/04/model-widget-controller-...

https://blog.metaobject.com/2017/03/concept-shadowing-and-ca...

"A view is attached to its model (or model part) and gets the data necessary for the presentation from the model by asking questions. "

https://web.archive.org/web/20090424042645/http://heim.ifi.u...


Well if the cookie comes from a third party it implicitly allows tracking.

Also AFAIK the Google Fonts question (is the IP alone already PII, if Google has no way of tying the IP to a person) has not been decided by the ECJ yet. There've only been decisions by lower level German courts that are still in dispute.


IP addresses are PII according to german law, that debate has been settled long ago by final court-rulings afaik.

"Well if the cookie comes from a third party it implicitly allows tracking."

Yes, because this makes tracking possible you have to gather consent first, regardless if tracking actually happens. Very bad solution, they could just define how data can be legally used, instead of also overreaching by defining how data can be legally transmitted.


Based on my understanding of the GPDR, this scheme wouldn't be anonymous, if the salt is stored anywhere. Using the salt(s) and the full list of all your users emails, you could find out which one matches the hash, thereby linking the analytics to the original PII. Of course that's a rather ridiculous idea, but AFAIK the GDPR doesn't put any qualifiers or limits on "it's not anonymous if the PII can be recovered with extra data".


It wouldn't if you own the full chain, i.e. have knowledge of both the on-device salt and hash AND the server salt and hash. However, in the scenario of using a third party like TelemetryDeck you as the developer would not be able to link the device hash to the server hash because you never see the latter.

So, my reading is: if there would be some mechanism (or a third party) which obfuscates the server hash so that you cannot tie it back to the device hash and only tells you "we've seen this device already" it could be anonymous. Of course, it also heavily depends on what other data you associate with that.


Google already announced the "Advanced Flow" that lets users override the verification. Yes, it's quite complicated, but it shows Google isn't trying to completely close down Android (yet). All this outcry is just lead to a boy who cried wolf situation. ADV is gonna become active, 90% people won't notice the rest will (begrudgingly) use the Advanced Flow. If Google then changes their mind actually does what F-Droid claims right now, nobody's gonna listen.

IMHO F-Droid is just mad because their store model of "developer publishes source code, F-Droid builds and signs the APK" would put immense liability on F-Droid. After all with that model F-Droid owns the private signing keys and now has to register them with Google. If they let a single malware app slide through, Google might designate F-Droid as a malware provider and block everything ever published on F-Droid. (Sidenote: Last I checked F-Droid had nothing in their policies that forbids publishing malware, just that it has to be open source) If you ask me this store model was always stupid and completely missed the point of having signed APKs. I think they also have a newer model where they don't own the private keys anymore, but there's still tons of legacy apps.

Of course Google might have been open to talks about some kind of verified app store program allowing F-Droid to operate under different terms. But that's certainly out the window after all the fear mongering, hyperbole and straight up propaganda F-Droid has put out in recent months.


Since Apples App Store is DMA compliant, the EU won't do anything against this far less restrictive change from Google.


If you're building the APK, you're probably installing via ADB, in which case none of the changes apply


There actually is in some regions. For example in Germany any publication must include an Impressum with details about the author and publisher. This requirement also applies to websites


It's actually worse. I just signed up with a dummy email and the page says they need your email to create an account so, they can store the icon kits you've created. That kinda makes sense. But at no point do they ask you whether you want to subscribe to any form of newsletter. AFAICT not even the privacy policy mentions anything about that. You're just subscribed automatically. So by definition anything not crucial for creating the account is literal spam. I'm not even sure that's legal under GDPR.

But the thing that might actually be killing their reputation is that their mails seemingly come from different emails all looking like bounces+18741050-ecba-jopudmulwqqsumjwub=nespj.com@email.fontawesome.com. But even worse than that, the "confirm your email" email and the following "finish account setup" email came from two different sub-domains. Maybe this is just a new attempt to get around Google's spam filter, but it seems like the worst thing you could possibly do when sending emails.


> But even worse than that, the "confirm your email" email and the following "finish account setup" email came from two different sub-domains. Maybe this is just a new attempt to get around Google's spam filter, but it seems like the worst thing you could possibly do when sending emails.

Standard advice is to use one subdomain for "transaction" email (verification, invoices) and another for marketing

https://www.twilio.com/docs/sendgrid/onboarding/email-api/ev...


That is standard practice because you will need to cycle that marketing domain until the end of time as its email reputation sinks into the abyss. Because people don’t want spam.


It's good practice because sometimes I don't feel like hitting the Spam button but I still want to black-hole the marketing e-mails. If you are also sending transaction e-mails through that address, then I have to decide whether to bother keeping you as a sender.


As strange as it is, but Austria is quite far ahead in terms of eIDAS since we've had Handysignatur for more than a decade. I wouldn't be surprised, if the Germans are planning to support hardware tokens, but haven't had the time yet.


> Austria is quite far ahead

Yeah, quite ahead in terms of making anonymous phone numbers illegal and requiring the government to know your phone number.

And if you don't want to use a smartphone, ID Austria does not work with regular FIDO security keys, you need special ones. Same for the old SmartCard system which didn't work without government-mandated malware.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: