Method definition overwritten warning for functions with default arguments

i still believe that this is a refactoring situation. if you are willing to decorate your function with the input types, you could just as well add some postfix, like barsym and barindex or whatever the integer means.

@Tamas_Papp The current semantics simply don’t permit the definition of multiple methods, which causes the overwrites in the first place. Then this could possibly be used to solve other related problems, such as the one we talked about earlier (with the _hidden_f).

@pint That would considerably reduce the flexibility of the API, especially when creating DSLs. My issue appeared while writing a DSL for a HTML-like (or rather HAML-like) templating engine. My goal was to come up with an easy to use syntax and provide helpful defaults. I dynamically generate methods corresponding to HTML elements, such as span(), p(), div(), etc. Using the do syntax this ends up looking very much like plain HTML. Doing things like div_empty or div_with_attrs or however you want to call them, would defeat the purpose.

I must admit I don’t really understand your proposal, but why can’t you do this:

bar(a::String) = "moo"
bar(i::Int) = "zee"

bar(::Type{String}) = bar("")
bar(::Type{Int}) = bar(2)

julia> bar(String)
"moo"
julia> bar(Int)
"zee"

?

2 Likes

that does not explain to me why do you want bar() to exist in two versions, one for omitted integer, one for omitted symbol (or any other signatures).

but if so, you can still abandon defaults, and provide your own default constant. like

const DEFBARINT = 7
bar(n::Int) = ...

bar(DEFBARINT)
# instead of bar<Int>() or bar(::Int)

after all, this is what “i want an int here, but i don’t tell what” actually means

Elaborating a bit:

What you want is for the user of your code to be able to specify the type when s/he wishes to use a default value, right? I prefer my own suggestion to yours in that case: that the user calls

bar("Hello", Symbol)

to indicate that the default Symbol value should be used. In that case you are left with needing a convenient way of generating those extra definitions. As far as I understand, that is what macros are for:

You make a macro with catchy name, like @usetypesasdefaults, and then

@usetypesasdefaults foo(a::String = "", b::Int = 2) = [...]

gets rewritten to

foo(a::String, b::Int) = [...]
foo(::Type{String}, b::Int) = foo("", b)
foo(a::String, ::Type{Int}) = foo(a, 2)
foo(::Type{String}, ::Type{Int}) = foo("", 2)

For many inputs this can cause an explosion of methods, so maybe it could re-write to

function foo(a::Union{String, Type{String}, b::Union{Int, Type{Int}})
    if isa(a, DataType)
        a = ""
    end
    if isa(b, DataType)
        b = 2
    end
    [...]
end

This is just off the top of my head. There are probably ways to simplify this.

Edit: One other advantage (aside from not breaking most Julia code ever written) is that the user now only has to specify the type of the missing input, instead of all the types (which might not even be known before run-time!!)

2 Likes

I don’t really want bar() to exist in 2 versions – that was just a generalization (simplification) as the discussion moved up from my initial question.

My exact use case is this: I build a DSL for constructing HTML pages - a templating language similar to HAML. I have some functions that generate an HTML document. And I want them to be used like this:

DIV() do 
  H1() do 
    "Hello world!"
  end
end

But of course, an important part of HTML are the attributes. I want to be able to pass them. So I want to do:

A([:href => "http://...", :class => "menu", :onclick => "loginModal()"]) do 
  "Login"
end

But then I wanted to make it easy on the user of the API and avoid that she forgets the parenthesis so I made them optional, allowing:

H1(:class => "main", :style => "border: 1px solid red") do 
  "Welcome!"
end

Finally, there are empty HTML elements – I need these too: HR(), BR() etc. Since they’re void it means they can’t contain any other HTML element so they won’t need the first argument, the Function; but can very well have the 2nd, for the attributes.

In my code I have something like this:

const NORMAL_ELEMENTS = [ :HTML, :HEAD, :BODY, :TITLE, :STYLE, :ADDRESS, :ARTICLE, :ASIDE, :FOOTER, ... ]

function attributes(attrs::Vector{Pair{Symbol,String}} = Vector{Pair{Symbol,String}}()) :: Vector{String}
  a = String[]
  for (k,v) in attrs
    push!(a, "$(k)=\"$(v)\"")
  end

  a
end

function normal_element(f::Function, elem::String, attrs::Vector{Pair{Symbol,String}} = Vector{Pair{Symbol,String}}()) :: HTMLString
  a = attributes(attrs)

  """\n<$( string(lowercase(elem)) * (! isempty(a) ? (" " * join(a, " ")) : "") )>\n$(f())\n</$( string(lowercase(elem)) )>"""
end

function register_elements()
  for elem_name in NORMAL_ELEMENTS
    f_body = """
      function $elem_name(f::Function = ()->"", attrs::Vector{Pair{Symbol,String}} = Vector{Pair{Symbol,String}}()) :: HTMLString
        \"\"\"\$(normal_element(f, "$(string(elem_name))", attrs))\"\"\"
      end
    """

    f_body |> parse |> eval

    f_body = """
      function $elem_name(f::Function = ()->"", attrs::Pair{Symbol,String}...) :: HTMLString
        \"\"\"\$($elem_name(f, [attrs...]))\"\"\"
      end
    """

    f_body |> parse |> eval
  end
end

It works quite well:

julia> Flax.DIV(:class => "main", :onclick => "go()") do
       "hello"
       end
"<div class=\"main\" onclick=\"go()\">\nhello\n</div>"

julia> Flax.HR()
"<hr>"

julia> Flax.BR(:class => "foo")
"<br class=\"foo\">"

but it causes Julia to output a long list of warnings as it generates overwriting methods. This is how it all started.

Now, I can find a way around them, I guess the simplest being by removing the version with the Vector. But I was wondering if there isn’t a way of avoiding that Julia creates this situation in the first place.

Would standard Julia keyword arguments work?

H1(class="main", style="border")

The Parameters.jl package may also be relevant:

1 Like

That’s interesting - and it does look pretty. I’ll try it out, thanks

Yeah, definitely, that’d be even cleaner and save quite a few keystrokes. If it also helps me get rid of the warnings and the parse |> eval parts, that’d be super awesome. I’ll see if / how I can use it. Thanks for the tip.

The syntax is something like

function H1(; class="main", style="")
     
end

(Note the ; at the start to separate positional arguments from keyword arguments.)

http://docs.julialang.org/en/stable/manual/functions/#keyword-arguments

if i understand correctly, using your proposal, you had to write something like

DIV<Function,Pair{Symbol,String}...>()

which is quite ugly. however, this particular case can be solved by making f required, making the attr array required, and creating a new methods with no function.

DIV(f::Function, attrs::Vector{Pair{Symbol,String}}) = # normal_element thing
DIV(f::Function, attrs::Pair{Symbol,String}...) = DIV(f, Pair{Symbol,String}[attrs...])
DIV(attrs::Pair{Symbol,String}...) = DIV(()->"", attrs...)
DIV(attrs::Vector{Pair{Symbol,String}}) = DIV(()->"", attrs)

this way, the missing attribute case is catched by the , version as empty tuple. so you can call with any combination of function and either attribute list or array. i included the type in the array generation, so empty arrays get the correct type instead of Any.