Why is Html a single DOM node?

Hi,

I was wondering about the Html type today. Do we have a compelling use case for the Html a as opposed to List (Html a)? In which cases do we really care that our argument is a single DOM node?

The reason to ask the question is because if we generally don’t care, the html library (or a wrapper?) can define its Html type to always represent a list of DOM nodes. The benefit is that can simplify types, that you don’t have to pick and that you certainly don’t need meaningless wrapper divs.

I’ve done a bit of research. The http library only has Html a in argument position on one function, which is map. Judging from that, such a refactoring seems feasible. At least two other areas remain: low-level details and HTML semantics.

I don’t really expect performance to change that much, because html is dealing with a lot of lists anyway. However, I don’t know how it’s implemented, so I really can’t judge this.

Regarding HTML semantics, at first I thought html has to be by itself, but is that really a requirement? For the DOM API it is, so probably it does make sense to have it as a low level type at least.

Browser has the element function which takes an Html a so that’s a use case. (what happens if I insert a text node in element? :smiling_imp:)

So my question is, which APIs or even elements did I miss? Do you want to enforce single DOM nodes in certain cases?

Update: naming idea: Node for DOM nodes and Html for multiple Nodes. Consider it a mass noun.

Update:

  • it makes the type signatures align with expectations from other APIs (thanks @pdamoc)
1 Like

Hi @roberth and welcome to the Elm community.

I’m confused a little bit. When you are talking about the Html a nodes defined in the Html library, how do you propose to declare the attributes of those nodes in your alternative?

For example, what would button [ onClick Increment ][ text "+" ] look like in your proposal?

1 Like

Hi @pdamoc,

It would look the same. You’d have

text : String -> Html msg

button : List (Attribute msg) -> Html msg -> Html msg

button [ onClick Increment ] (text "+") : Html msg

The button function gets all the information to construct the button, as before.

A big difference why this can be done in Elm html and not in many other libraries is because html doesn’t have any setter functions to modify an element. In fact it can not even have setters because you can’t set an attribute on a text node.

html would have concat function, none : Html msg and a ++ operator would be really nice.

Update: empty -> none

1 Like

I find it very confusing to have the type of an HTMLElement be the same as the type of its children.

I’m trying to understand what would the benefit of your approach be. I’m also trying to understand what problem are you actually trying to solve here with this redesign.

1 Like

The real-world problem that this solves is the need for meaningless div [] to turn List (Html msg) into Html msg because someone wrote a function that has an Html msg parameter.

This also means that the community doesn’t have to teach newcomers to always write List (Html msg) parameters instead of Html msg. I think Elm does a good job of making it easy to do “The Right Thing”, but this stands out to me.

I agree that the type of element is a slightly less descriptive with this change, but I don’t think that’s a problem. The difference between “a list of nodes” and “more HTML” seems insignificant to me.

1 Like

If someone’s function expects a node, give it a node. I think this is perfectly fine to wrap a list with div []

If you don’t like to have an extra div and would have preferred that that function receives a list, you are free to reimplement it to match your preference.

A List of children should look like a List. It is less surprising to see List (Html msg) than to discover that an element is actually a list.

It might be so for you but the difference between “a list of nodes” and “a node” is significant. It is easier to model mentally an HTMLElement to one thing than to a list.

If you really want to avoid extraneous divs, feel free to delay the construction of the container. In other words, have your functions return List (Html msg) and concatenate them like this :

firstPart : Model -> List (Html Msg) 
firstPart model = 
    ...

secondPart : Model -> List (Html Msg)  
secondPart model = 
    ...

someSection : Model -> Html msg 
someSection model = 
    section [] ( firstPart model  ++  secondPart model  )

someSectionAlt : Model -> Html msg 
someSectionAlt model = 
    [ firstPart model 
    , secondPart model 
    ...
    , lastPart model 
    ]
    |> List.concat
    |> section []
1 Like

Indeed I have become more inclined to just use List (Html msg) in most parameters now and doing so works.

Your example illustrates this approach nicely.

It also has an interesting typo. You wrote List msg because you were thinking about lists instead of HTML.

1 Like

Good catch. I have updated the code to the correct form.

1 Like

What kind of voodoo did I just witness? If this doesn’t make a compelling point, I don’t know what does :sweat_smile:

This is a great question and discussion so far.

@roberth I think the primary reason List is used is because Elm functions cannot have a variadiac interface. A parameter can only be bound to a single value, but that value could indeed be a list containing multiple values.

Using your proposed change to the API, how would you represent this Html unordered list?

  • one
  • two
ul [] (li [] (text “one”))

Where should we insert li [] (text “two”)?

Using List a is the way for us to understand that a function accepts a variadic (0 or more) number of a-typed elements. As much as I love your idea to allow the programmer to forget such complexity, I believe this is a limitation in the language itself - Ie, there’s no way to express a variadiac interface in a language where all functions are curried and lists (or other compound data types) are the only option for specifying a variable number of arguments.

(This is also the reason we sometimes see things like map2, map3, … map9)

An Html msg type is (conceptually) still appendable and appending still has a neutral element (like [] but named Html.none). One might even say it’s a Monoid (/me takes cover). So ideally it’s like

ul [] (
  li [] (text one ++ text one) ++
  li [] (text b)
)

Is it ok to overload ++?

Instead of overloading ++ we might define Html as a type alias type alias Html msg = List (HtmlNode msg).

I think elm-ui resolves this problem by highlighting the semantic differences. Most elements can only have one child and so only take another Element msg as an argument. Those that have multiple children (and so take List (Element msg)) do so for a reason made obvious in their name, e.g. Element.row or Element.column. Not only does this make it much easier to work out which you need to hand to a given element, but what you have and what the element expects are more often than not already the right thing anyway. If you haven’t looked at it, it’s definitely worth checking out…

2 Likes

@Jess_Bromley, elm-ui seems to provide a really good ‘language’ to describe user interfaces.

This library is a complete alternative to HTML and CSS.

Sadly that’s too big a change for us right now, because it’s very compelling.

HTML semantics seem to embrace its “rose tree” format and always provide some meaning for the juxtaposition of siblings. elm-ui doesn’t seem to leave that meaning implicit and only defines it for elements that have the explicit purpose of combining elements, such as row. Interestingly, it does define Element.none, so Element msg seems to be a bit like Maybe (Html msg) (where Html has the usual definition).