type Msg = Key String | NoOp
update msg model = case msg of Key keycode -> handleCode keycode model NoOp -> model
Keyboard.presses (\keycode -> if keysOfInterest keycode then Key keycode else NoOp )Sub.filter : (a -> Bool) -> Sub a -> Sub a Keyboard.presses
|> Sub.filter keysOfInterest
|> Sub.map Keyfilter : (a -> Bool) -> Sub a -> Sub a
filter fn s =
if fn s then s else Sub.noneHow does this address the use case from the original message in this thread? I don’t think it does. The aim here is not to turn on/off whether there is a subscription, but instead to have a subscrption that lets through only some events from Keyboard.presses.
The point is that NoOp is the way to do right right now. Look at the other examples for that use case.
That other example is equivalent to what the OP gives as example code in the first message here, right? The code he tries to ask for an alternative about.
If you want to disable/enable a subscription then you need to handle with your subscriptions function.
Yes, that’s clear.
There is no concept of “filtering” a subscription in this way.
That there isn’t is also clear. The question of this thread is, maybe there should be, to not always force NoOp messages and the extra rounds of the update loop they force?
filter : (Sub a -> Bool) -> Sub a -> Sub aTo unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.
Just for the record: Adding a Sub.filter function would not make Sub “monadic” (in the “Haskell Monad“ sense).
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
I’m not Tobias, but I do have an inkling of what makes a solution like the 0.17 version of the first example at http://noredink.github.io/posts/signalsmigration.html#filter feel “imperative style”.
For me, it’s the necessity of having a NoOp message. That’s a general phenomenon: Whenever modelling something in TEA requires a NoOp thing, I feel like the API is not quite right yet.
Why? When I teach students, one thing that comes up reliably in discussions of differences between declarative and imperative languages is the issue of whether the language’s if-then construct always needs an else. Since declarative languages are typically expression/value based, the answer for them is typically “Yes”. Since imperative languages are typically statement/instruction based, the answer for them is typically “No”. That’s why in Haskell and Elm and other functional languages an if-then-else without the else-part makes no sense, whereas in C etc. there are lone then-branches or empty statements.
So when I see that one has to add an “empty statement” kind of thing (a NoOp constructor) to some program in Elm to work around the non-availability of some piece of API (here Sub.filter, but also in other discussions, e.g., about Effects.silentTask), I always have the feeling that something’s wrong. Maybe that’s weird, but that’s how it is for me.
I don’t know whether SetCurrentTime Nothing feels “as imperative” as NoOp, but it does still feel like some modelling decision taken away from me. Maybe I don’t want to have a concept of SetCurrentTime Nothing, under whatever name.
Previously the type used in that example would have been
type Msg = SetCurrentTime Time
Because there is no possibility to filter events, one is forced to change the type to
type Msg = SetCurrentTime (Maybe Time)
or
type Msg = SetCurrentTime Time | NoOp
In both cases, intent is taken away from the modeller.
You suggest it’s good because it makes it more explicit that there will be no change in some circumstances. I don’t really buy that, at least not as universal truth. Consider this case: Currently, the keyboard package contains a Sub for presses. Assume it would also contain a Sub for arrows (just firing for arrow key presses). Would it be bad that a program that only wants to react to arrow keys uses arrows instead of presses? Would it always be better, because more explicit, that the program takes in presses and then explicitly has to say in its update function “oh I’m not interested in non-arrow keys, so while I do have to take them, I’m not gonna do anything for them”? It could be considered more declarative to be able to say up front that one is only interested in arrow keys for this program, so wants to subscribe only to arrows, not to presses.
Hi Noah, I see this message only now, but I guess the answer to Peter I have been writing and sent a minute ago conveys that I would not always prefer it that way. (Sometimes yes, I would want to model this decision in the update function, but not always.)
Also, ever since the Effects.silentTask discussion, a comment by Richard has been coming to my mind when encountering NoOp. It was based on looking through the NoRedInk code base, and yes, it is about a different reason for having NoOp, but still, it does to me signify something of NoOp being a possible antipattern in itself. Being able to rid an application’s Msg type of NoOp means to gain a static guarantee. For example, one is freed of making sure “by hand” that one actually does treat NoOp by not changing the model at all. If NoOp is creeping back in, I would be worried.
Sorry, I didn’t really address the suggestion that the solution given for the first example at http://noredink.github.io/posts/signalsmigration.html#filter isn’t the one it should be anyway. NoOp-aversion was still on my mind. :-)
So, the suggestion is that that solution should be different, moving the if-then-else into the update-function (and not having NoOp on a mapped Sub). A potential problem I see with this is when there is more than one update-function. Several components in a TEA setting, somehow combined. Couldn’t the new suggestion mean that one has to duplicate the if-then-else logic in several update-functions?
I imagine a situation where one wants to handle only arrow keys in several sub-components of an overall program, and where one has the following three potential choices:
Turn the presses Sub into an arrows Sub by discarding unwanted keys (at one place), subscribe to arrows.
Turn unwanted keys into NoOps by mapping over the presses Sub, subscribe to the mapped Sub, and extend several update-functions each by a case reacting to NoOp by not changing the model.
Directly subscribe to the presses Sub, put essentially identical if-then-else logic into several update-functions.
In the absence of a Sub.filter functionality, the 1. choice is not available. But I am concerned that not always at least one of 2. and 3. is a better choice than 1.
http://package.elm-lang.org/packages/elm-lang/html/1.0.0/Html-Lazy may be at least a partial solution to this specific concern.
So, the suggestion is that that solution should be different, moving the
if-then-elseinto theupdate-function (and not havingNoOpon a mappedSub). A potential problem I see with this is when there is more than oneupdate-function. Several components in a TEA setting, somehow combined. Couldn’t the new suggestion mean that one has to duplicate theif-then-elselogic in severalupdate-functions?
It’s not clear to me that it will always be possible/desirable to manage things that way. But I also have no counter proof, not having built big TEA stuff myself. In a technical sense you are right, one could even artificially put a new component at the very top of the hierarchy whose only job it is to do the event filtering, only passing the arrow key events down through what would otherwise have been the top-most component. But will this work in practice? When there are potentially several subscriptions that need to be managed? When we do not only have to think abotu whether a given program can be written, but also whether a program can be maintained over time, whether it is easily refactorable, etc.?
Moreover, (how) does this actually work in the presence of encapsulation? Say we have components A, B, C. A has B and C as children, but should be decoupled from their implementation. In particular, A should not know anything about the message types of B and C, it should only pass messages through to them. B and C both want to subscribe to arrow keys. If there were a arrow Sub, B and C would both subscribe to it, and A would simply Sub.batch the subscriptions of B and C, not look inside them. Everything nicely decoupled. Now, since there is no arrow Sub, you propose that A takes responsibility for turning events from the presses Sub into arrow events of the kind B and C like. But for this to be possible, A has to give up its ignorance of what B and C are and want. Seems to destroy some aspect of TEA.
--
subscriptions : Model -> Sub Msg
subscriptions model =
if model.chatClosed then
Sub.none
else
Websocket.listen "ws://echo.websocket.org" NewMessage
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.filter (\_ -> ~model.chatClosed) (Websocket.listen "ws://echo.websocket.org" NewMessage)
Couldn’t an analogous argument be made to show that List.filter should not exist either? Because of the danger that programmers badly write List.length (List.filter (\_ -> flag) expensiveGenerator) sometimes instead of if not flag then 0 else List.length expensiveGenerator?
Your argument relies on the danger that programmers may consider the two definitions of subscriptions you give to be interchangeable. They aren’t, and we would have to make sure that programmers know that they aren’t. Noah has repeatedly in this thread pointed out the importance of actually turning subscriptions off whenever it is known from the current model alone that no events at all are needed from them. That applies independently of whether Sub.filter is available or not. (Also in the 2. or 3. case from my earlier message a programmer could go wrong and have a subscription running even though it’s knowable from the current model that all the subscription’s events will turn into NoOp or will be ignored in the update function due to some model-but-not-event-dependent condition.)
Luckily, there is a simple rule to tell programmers: “Never use Sub.filter with a predicate, produced from the current model, that does not actually use its event(ual) argument.”
(In your example, the Sub.filter (\_ -> would have loudly screamed: DON’T.)
middleware :: (Msg -> Html) -> Html -> Model -> Msg -> Html
onlyEvenTicks nextMiddleware currentHml currentModel msg =
case msg of
Tick time ->
if isEven time then
nextMiddleware msg
else
_ ->
nextMiddleware msg
Was your 0.16 code using Signal.forwardTo inside the view function? If so, there’s a good chance that 0.17 will please you with much better Html.lazy behavior.
You are aware that the functions from Html.Lazy use reference equality? So two models are only considered equal if they are the same JS object (at the the same pointer address). The zipper data structures I know don’t give that degree of equality after moving down and up again.
y = { x | a = x.a }