On `none` patterns

Currently, passing Http.header "" "something" in a List of headers in elm/http ends up in a runtime exception. I have opened an issue for this.

By the way, while I writing up the issue, I pondered this a little more generally.

Background

Essentially, I wanted to do this:

headers =
    [ case fooBar of
        Foo str ->
            Http.header "foo" str

        Bar ->
            Http.header "" ""
    , ...
    ]

I.e. Conditionally add/not add a value within a List.

Yes, the runtime exception is avoidable if I rewrite to this:

headers =
    case fooBar of
        Foo str ->
            [ Http.header "foo" str ]

        Bar ->
            []

But what if I need to add other elements to the list?

let
    fooHeaderOrNone =
        case fooBar of
            Foo str ->
                [ Http.header "foo" str ]

            Bar ->
                []
in
[ ... (other headers) ... ] ++ fooHeaderOrNone

Works, but do we add this kind of conditional variables for all non-static parts?

Maybe we should. Since descriptive intermediate variables actually could make a whole part easier to understand.
But it is also very likely that it feels tedious to do so, especially the intent behind those switching is just obvious, and there are only few of them. Also when I am too lazy to think about variable name. Super likely!
(Of course, if there are many switches to toggle, then we should definitely consider breaking it up to descriptive private functions)

As you may noticed, the pattern is very common in Html or Html.Attributes:

div
    [ case fooBar of
        Foo _ ->
            class "foo"

        Bar ->
            class ""
    , ...
    ]
    [ case bazMaybe of
        Just baz ->
            bazElement baz

        Nothing ->
            text ""
    , ...
    ]

none patterns

Let us call this "none patterns".

none patterns are very common, common enough for some modules actually provide APIs with exactly that name: notably Cmd.none or Sub.none

Cmd.batch
    [ staticCmd
    , case fooBar of
        Foo foo ->
            fooCmd foo

        Bar ->
            Cmd.none
    , ...
    ]

I like them as they are handy for some small, obvious switches inside Lists.
I like them also because they are somewhat format-friendly. Say if we break above code into variables, without using Cmd.none:

let
    fooOrNone =
        case fooBar of
            Foo foo ->
                [ fooCmd foo ]

            Bar ->
                []
in
Cmd.batch
    ([ staticCmd
     , ...
     ]
        ++ fooOrNone
    )

Well, it is subjective I admit, but it feels ugly to me, due to introduced parens, ++, and their formatting rules. Of course we can remedy that by introducing an additional variable for static parts:

let
    staticCmds =
        [ staticCmd
        , ...
        ]

    fooOrNone =
        case fooBar of
            Foo foo ->
                [ fooCmd foo ]

            Bar ->
                []
in
Cmd.batch (staticCmds ++ fooOrNone)

I am more comfortable with this, but compared to the original style using Cmd.none within List, it still feels like a roundabout approach. Yes it brings some additional info for readers, but also, visual complexity.

Again, it involves subjective feelings. And if there are more switches in a List, I would happily consider introducing well-structured private functions or let variables. But if there are only one or two of them, none pattern could be just enough.

For Html and Html.Attributes which do not have none functions, text "" and class "" (or style "" "", or even property "" Json.Encode.null) are widely used for this very purpose. For that, elm-ui even adopted the pattern as Element.none, which is more sophisticated than just putting text "", though.

For text "" and Html.none, it is indeed tracked by issues already: elm/http#72, elm/http#53

Discussion

I see questions or discussions about this pattern often, especially around Html or elm-ui. (e.g. “How can I do the same thing as Element.none for attributes?”)
And again I found a case where this leads to even runtime exception in the case of current implementation of elm/http.

So I think it’s worth a consolidated discussion around this.

  • Do you like or dislike the pattern, and why?
  • When you think you should or should not use this pattern?
  • When, or What types of, packages/modules can benefit from this pattern? “Should I consider adding none to this package?”
  • Is there any possible issues when adopting this pattern?

Personally, I like this pattern for reasons explained above, and hope more packages support none-equivalent funcitons if they make use of “Listable” types. Http.Header in elm/http is one of them. Element.noneAttr in elm-ui would be good to have too. I always introduce it anyway.

Format-friendliness is especially pleasing. When I make views with elm-ui, writing somewhat long attribute lists is quite common, which I hesitate to disrupt with ++ varyingAttrs!

Appendix: idiom using List.filterMap

Some of you may noticed that it is also possible to mimic none pattern using List.filterMap and Maybe a:

Cmd.batch <|
    List.filterMap identity <|
        [ Just staticCmd
        , case fooBar of
            Foo foo ->
                Just (fooCmd foo)

            Bar ->
                Nothing
        , ...
        ]

I often use this, especially when there are related functions that returns Maybe a already.

2 Likes

I use no-op functions all the time with Html (text "") and Html Attributes (class ""). I find that using these is a lot more natural than using Maybes or lists in these situations.

Definitely would like to see explicit functions for this.

2 Likes

I use filterMap : (a -> Maybe b) -> List a -> List b

2 Likes

Very nice write-up of something that I at least find common. I tend to appreciate the “none” value when it is there.

Having a batch value sort of also solves this problem. In your command example where you hypothetically do not have access to Cmd.none, you can always give Cmd.batch [] as the none value (which is how Platform.Cmd.none is defined). Or you can use the other approaches but Cmd.batch to join together the lists, eg:

let
    fooOrNone =
        case fooBar of
            Foo foo ->
                [ fooCmd foo ]

            Bar ->
                []
in
Cmd.batch
    [ staticCmd
    , ...
    , Cmd.batch fooOrNone
    ]
    )

Which is really just equivalent to using it in the let declaration as Cmd.batch [].

1 Like

It has occured to me at various times that this is a good idea; so its helpful that you took the time to write it up.

I take this as a nudge to API authors to try and always include a none option in your DSL list member constructors. I shall add it to some APIs that I have been working on recently.

2 Likes

Very good point, thank you for bring it up!
Yes, having batch function might actually be more generally helpful for this. As you pointed out,

  • It IS actually a building block for none function
  • Even without none, it allows us to do the equivalent by batch []
  • It essentially enables us to flatten Lists!!

I found the 3rd point very interesting, related to code modularity.
As we are already using Cmd.batch to do things like this:

fooCmd : Cmd Msg
fooCmd =
    Cmd.batch [ fooDoThis, fooDoThat ]

barCmd : Cmd Msg
barCmd =
    Cmd.batch [ barDo ]

rootCmd : Cmd Msg
rootCmd =
    Cmd.batch
        [ rootDoThis
        , rootDoThat
        , fooCmd
        , barCmd
        ]

(Of course we throw in Cmd.map when some of them are within their own Msg scope)

If inlined, it looks like:

rootCmd =
    Cmd.batch
        [ rootDoThis
        , rootDoThat
        , Cmd.batch
            [ fooDoThis
            , fooDoThat
            ]
        , Cmd.batch
            [ barDo
            ]
        ]

This flatten-ing is, although verbose and manual, quite powerful I think since it cannot be done with ordinary List.flatten.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.