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"
?
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!!)
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:
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.