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.