2
\$\begingroup\$

In the Elm examples I have seen for form handling, they attach each input element with an update handler that sends out a very specific message to update the model according to the new input state.

So you end up with a lot of message definition in your Msg union type.

Alternatives I have seen for "dynamic" or complex forms use generic handlers that can work with Dict of data, keyed by form input id, at the expense of some type-safety.

I was thinking I can have (at least for simple handlers that only update the model in straightforward ways and do not need to send Commands to the runtime engine) a middle-ground with a generic SimpleModelUpdate message that takes a mutator function as its payload to describe what it is actually supposed to do.

Something like

 type Msg =  ....
   | SimpleModelUpdate (Model -> Model)

 update model msg = ...
   case SimpleModelUpdate mutation ->
     (mutation model, Cmd.none)


 Html.button 
  [onClick = SimpleModelUpdate (\m -> {m | counter = counter + 1})]
  [text "Increase"]

That seems to work. And I like it because I do not have to spread the code for trivial updates all over the place (I can reserve dedicated message types for the really "important" stuff).

Is this bad design? Are there other issues that I am not seeing?

\$\endgroup\$
3
+100
\$\begingroup\$

I think the general consensus is to avoid functions in Msg constructors when possible.

The one certain thing that will break is the Msg history import/export of the Elm debugger, since functions cannot be serialized.

Evan included this comment in his Sortable Tables example:

One of the core rules of The Elm Architecture is never put functions in your Model or Msg types. It may cost a little bit of extra code to model everything as data, but the architecture and debugging benefits are worth it.

Here's some commentary by Zoul on Discourse:

Generally speaking, in Elm you model an app with explicit state modified by a pure function based on messages with clear semantics. This makes it very easy to reason about the state changes: it’s quite obvious what state the app is in and what state transitions lead to this state.

Once you start using functions in messages, this model gets much more powerful, but much less transparent. You lose the clear distinction between data and code – the messages no longer describe what you want done, they implement a part of it. You cannot see the behaviour just by looking at the update function anymore. And comparing or serializing the messages gets more complicated.

In the end, it’s probably not worth it.

Besides breaking the debugger for yourself (and for others, in case you publish a package violating this principle), I don't know of any kind of language problems you may run into if you go down this path, and the above comments are the norm for this question: vague warnings about never putting functions in messages but not much specificity into the why not of the question besides the debugger issue.

But in the end, if the language designer says to never put functions in your Model or Msg types, in bold text nonetheless, I'd defer to their judgment on the matter.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.