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

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)”?

Save us from what?

This is essentially a question of style — for generic programming, ideally all methods of a function do something similar. But there is nothing in the language enforcing this, and for your own types it is not even type piracy. So one can do

struct WackyType end
Base.div(::WackyType) = :wack

pretty much without any major adverse consequences; it is just poor style.

Minor consequences include confusing users using methods, documentation, and similar.

1 Like

One additional option would be to turn all of the tags into string macros instead of functions:

div"hello world"(; class="shadowbox")

EDIT: nevermind. This doesn’t work for nested elements. Unless you instead had the contents of the string macro be irrelevant:

div""("hello world")
julia> methods(div) # I see my added test div buried in the middle...:
# 59 methods for generic function "div":

[29] div(s::AbstractString) in Main at none:1

[32] div(x::Bool, y::Bool) in Base at bool.jl:110

I wouldn’t worry about beginners looking at methods, I would at least worry more about div not being available, in the simplest form for HTML (I note, it’s rare to use div for division, and ÷ preferred by me, if not / or other operator, most users would).

This is more confusing:

julia> div(false, false)
ERROR: DivideError: integer division error
Stacktrace:
 [1] div(x::Bool, y::Bool)
   @ Base ./bool.jl:110
 [2] top-level scope
   @ none:1

julia> div(false, true)
false

If this is only about style and actually always working, I’m ok with impurity, want to make sure I’m not overlooking something (invalidations? making code loading slower?), e.g. could something like this be an issue:

[9] div(a::DataValues.DataValue{T1}, b::T2) where {T1, T2} in DataValues at /home/pharaldsson_sym/.julia/packages/DataValues/N7oeL/src/scalar/operations.jl:65

[13] div(::Number, ::Missing, r::RoundingMode) in Base at missing.jl:122

This is just division by 0, what does this have to do with the topic we are discussing? :man_shrugging:

2 Likes

I did participate in this thread, where I was opposed to your opinion. I was a bit more in agreement with Jeff Bezanson. I actually wrote some software in reaction to this thread, see
Using_merge.jl

Aye, but that requires using a HtmlString or some other type that is defined in the package and the whole purpose is to decrease boilerplate so that would probably defeat the purpose.

For example, stderr in I/O context is one thing but in statistics is another. Would having it do two very different things sharing the name, why not? It is not context ambiguous. The issue is avoiding type piracy. Other than that, just make sure that the new methods have a clear docstring to avoid confusion.

Yes, stderr is a good example of a term which has different meaning in different contexts, which is
in my view the exact case were kristoffer’s assertion is needlessly limiting what Julia can do.

Another example where I “pirated” Base in my package is that in mathematics, conjugacy in a non-commutative group (that is, the conjugate of g by h is inv(h)*g*h) is denoted like exponentiation
by a superscript g^h. It is thus natural to define

Base.:^(g::GroupElement,h::GroupElement)=inv(h)*g*h

An example of conjugacy in a non-commutative group is conjugacy of matrices, and I regret that
there is no notation for it in Julia (as far as I know).

Maybe I am missing some context, but if GroupElement is a type your package owns, the above code is not type piracy.

Yes, in my view it is not piracy. But I guess it is in Kristoffer’s view which is why I used the term.
And I hope that in your view it is not poor style either…

Kristoffer never said it was piracy, he said it was a bad idea because it makes it hard to know what generic code does. The term I hear people use for this is ‘pun’.

Piracy is a bad idea because it breaks code, or at least makes it harder to not break code.

5 Likes