Base.div vs Html.div - specialize or not?

Dear Julians, I have a major dilemma. I’ve been going back and forth between how to handle this and I was wondering if there’s any best practice?

There is a Html.div function which creates a HTML <div>...</div> element. This is part of Genie’s HTML rendering API, together with many others, corresponding to the rest of HTML elements. These are exported to be used for writing expressive HTML UI code:

section() do 
  h1("Hello")
  p("Lots of text")
end

The issue is that Html.div conflicts with Base.div which is automatically exported by Julia. This makes Html.div an exception in the Html API in that it needs to be written as Html.div(...) or explicitly imported. This is ugly and verbose and can trip people new to Julia. So from a usability/principle-of-least-surprise perspective I’m all for specialising Base.div.

However, my left brain hemisphere screams that from a semantic standpoint Base.div and Html.div are totally different, and that it’s not specialisation as Html.div does not introduce a new division method.

In the end I’m partisan to doing what’s more natural for the users (least surprise is a big deal for me and a user of the Html API expects div to work like any other methods) but I was wondering what’s the community recommendation when dealing with such name conflicts, if any.

4 Likes

I concur with the thought process.

I would not extend Base.div, that is for division.

Regardless of the existence of Base.div, I think that using Html.div, Html.p etc are much more clear anyway, so personally I would just not export any of these.

5 Likes

Normally I prefer not exporting functions from packages as far as possible, but in this case we’re talking about code like

Html.div .... 
   Html.div ....
      Html.p ....
          Html.a ....
   Html.br
   Html.div
      Html.div
         Html.div

etc … which seems a bit toooo verbose

I’d pick a new name. Hyperscript.jl uses m(). Similar JavaScript packages use m() or h().

An option for the user is to do const H = Html to save a few characters, and then you can prefix everything.

2 Likes

I guess, but you can always do

using Html: div, p, a

then.

5 Likes

How about html_div?

Thanks for the input, much appreciated!

In the last few months I tried various approaches (and it’s true, this is an edge case in that the Html API is very large, which won’t be the case with most name conflicts with definitions from Base). My observations where:

  1. when one has a 20-30-50 lines of HTML UI code, prefixing adds a lot of noise and extra typing. Also, it’s very clear from the context that we’re dealing with an HTML div so prefixing does not add value. This is more of an issue of a DSL really. @fredrikekre

  2. in the beginning I didn’t export but I noticed that when rendering a reasonably complex HTML UI, explicitly listing all the used tags was a tedious process (imagine having to manually import some 20-30 tags). Also it was annoying as I would always forget to declare them, error, argghhh. @fredrikekre

  3. @Mason I’ve seen this used in some other package’s APIs, but for consistency it means all the elements would have to be prefixed, so we’d end up with html_p, html_h1, etc. And in this case we’re better off using Julia’s “natural” namespaces, the modules, e.g. Html.div.


A “think outside the box” idea I just got would be to build a DSL out of constants. Then all the elements would be defined as SPAN, DIV, H1, P (uppercase tags would feel so 90s :)) ). But it seems very uncomfortable to type that many uppercase words… Although the idea might have some potential.

1 Like

How about an @html macro that just does find and replace to namespace things like h1, p and div so that they don’t need to be exported?

e.g. it would just take

@html section() do 
  h1("Hello")
  p("Lots of text")
  div("boo")
end

and turn it into

section() do 
  HTML.h1("Hello")
  HTML.p("Lots of text")
  HTML.div("boo")
end

That way, there’s a simply macro people can use but they can easily avoid it by just namespacing with HTML if they desire.

10 Likes

At one point, I started an experimental JSX.jl :nerd_face:

If you don’t have to define div don’t do it and instead, let the users define their custom tags.

In AcuteML, I made HTLM/XML tag names independent of the Julia types. This allows high level of flexibility without unnecessary code bloat. I can pass any Julia type (String, Number, Array, etc) to a field without being worried about its name.

If your library is only used for HTML5, then it makes sense to define a type and call it div. However, for a general library like AcuteML that supports any arbitrary XML/HTML code with any tag names, this approach is not suitable.

using AcuteML
@aml mutable struct Body "~"
    group::Vector{String}, "div"
    heading, "h1"
    heading2, "h1-nonstandard"
    foo, "mytag"
end

AcuteML is like a Julia version of JSX. :yum: In the above, I defined Body as a component. Now, I can use it as a Julia type with any tag name!

@aml mutable struct Page doc"html"
    body::Body, "~"
end

I am pro-extending functions. Methods can have different “definitions” based on the context / arguments. That’s the point of multi-dispatch. div(::AbstractString) however would fall into type piracy though which is the only reason why I would not favor it.

I would favor such solutions.

An other way of implementing the same kind of idea would be to have

@html section() do 
  h1("Hello $(div(5,4))")
  p("Lots of text")
  div("boo")
end

expand to something like

@html section() do 
  # Local function that dispatches to Base.div or Html.div
  div(args...) = Base.div(args...)
  div() = Html.div()
  div(x::AbstractString) = Html.div(x)

  h1("Hello $(div(5, 4))")  # -> calls Base.div(5, 4)
  p("Lots of text")
  div("boo")                # -> calls Html.div
end

Ideally two methods should belong to the same function when they do more or less the same thing. This makes writing generic code very easy .

Here this does not hold, so I would recommend that you have a separate Html.div.

2 Likes

If only there was a way to bring symbols in from a module without using :P.

julia> module Foo
           div(x) = print("<div>$x</div>")
       end

julia> using .Foo: div

julia> div("hiyo")
<div>hiyo</div>
3 Likes

I disagree with the viewpoint “div does division so it should never be used for another purpose”.
This leads to people handicapping themselves, and then trying to find dubious justification for
“creative” use of div. For me a method:

Base.div(x::foo)=...

is not pirating if the type foo is specific to your package. I do not see in which way this could
introduce a conflict with Base or any other package. Thus a definition

Base.div(x::AbstractString)=...

is pirating (because if two packages do that there will be a conflict) but a definition

Base.div(x::HtmlString)=...

in a package devoted to html is not pirating.

Maybe a new language feature would be useful: a new import variant that allows for hiding several symbols. Haskell has that and I think it is quite handy:

import Base hiding (div)

In Julia it could maybe look something like this:

hiding Base: div

Though, this would need a baremodule or so, and it wouldn’t help users of older versions of Julia. I’ve opened an issue on GitHub.

https://github.com/JuliaLang/julia/issues/37925

3 Likes

So for this specific use case you mean this?

using HTML # to import div and others
using HTML: div # to resolve naming conflicts

You are of course free to use div to mean whatever you want in your own package but if you extend Base.div it should mean the same. Otherwise, you have no idea what generic code means because you don’t have concrete types available at that point. Maybe you forgot Function name conflict: ADL / function merging? where this was explained in detail.

3 Likes

Base.div is always binary numeric arguments (or with third optional argument), while for HTML always (one?) string. Maybe I have “no idea”, but does it not save us if div for HTML is always a unary function? I’m thinking of the practical implications of specializing. I looked at your link to a much longer discussion than I have time for now… and from there a link to:

I just know Julia for web programming, is also very important, so I hate to have this dilemma, and would like the most natural div (without needing macros or prefixing). Is this about mostly about the help text for div, that can’t be(?) extended. Can we allow for some impurity here?Would it make something slower?

SInce ÷ points to div (also for the help text), you could do very confusing…:

julia> ÷("text for div")
text for div

About the help text, should it be “x ÷ y” rather than current “÷(x, y)”?