I’ve stumbled onto some things that I don’t quite understand - it all started with me trying to get join
to generate empty strings ""
when joining nothing
s (Void
s).
Between string
, String
, convert
and string generating functions like join
, it seems to me that behaviour should be consistent.
Example:
julia> test_arr = [nothing, nothing]
2-element Array{Void,1}:
nothing
nothing
julia> join(test_arr)
"nothingnothing"
julia> import Base.string
julia> string(::Void) = ""
string (generic function with 14 methods)
julia> string(nothing)
""
julia> join(test_arr)
"nothingnothing"
julia> convert(String, n::Void) = ""
convert (generic function with 1 method)
julia> join(test_arr)
"nothingnothing"
julia> String(nothing)
------ MethodError --------------------- Stacktrace (most recent call last)
[1] — String(::Void) at sys.dylib:?
[2] — String(::Void) at sysimg.jl:53
MethodError: Cannot `convert` an object of type Void to an object of type String
This may have arisen from a call to the constructor String(...),
since type constructors fall back to convert methods.
-
join
does not use my string
nor my convert
functions - I’m wondering what does it use to convert to strings?
-
String
doesn’t use my convert function either?
What am I missing?
Thanks
A
2 Likes
See doc of string
In general you should overload how printing is done
String
is totally different. It is an explicit conversion and not printing.
Thanks, I’ll give it a go.
So then, join
uses internally one of the print
, show
, display
, etc family of functions?
Hmmm, looking into the docs:
String(s::AbstractString)
Convert a string to a contiguous byte array representation encoded as UTF-8 bytes. This representation is often appropriate for passing strings to C.
Due to the uppercase “S” I expected this to be a string
constructor, similar to SomeType(...)
- and thus behave pretty much like string
. It seems that’s not the case.
@yuyichao
Just to be 100% clear, my issue is not one of printing in the REPL. It is about changing the actual value returned by join([nothing])
, from “nothing” to “”.
My use case is closer to this:
julia> [if false
"moo"
end] |> join
julia> "nothing"
Yes, though not because of the upper case, it is the constructor.
No. A constructor is basically an explicit constructor. Printing is totally different and that’s what string
does as shown in the doc.
That’s not what I’m talking about either. All of the functions you mentioned are wrappers around printing objects and so that’s what you should overload. If you overload one of the wrappers (e.g. string
) to not do what the doc says the functions should be doing, it’ll certainly not be consistent with other functions anymore.
And as for the convert
. You are overloading (or not overloading at all) the wrong function with the wrong syntax. Use Base.convert(::Type{String}, ::Void)
. Also note that you should not overload this particular method other than for test purposes.
Thanks very much @yuyichao
You’re right I don’t know what the heck I was trying to do with that convert
! Thanks for pointing that out
Usually it is a bad idea to change the behavior of a function from another module (here, Base.join
) with a type from another module (here, [nothing]
is of type Base.Array{Void,1}
). It is fine to overload a function from another module, but the rule of thumb is that at least one of the argument types should be specific to your module.
Probably there is a better way to do what you are trying to do. What underlying problem are you trying to solve?
2 Likes
@stevengj Thanks!
I’m building a templating system (think Mustache, but more like Haml). It uses Julia itself. It allows doing things like:
<html_here>
<% if is_logged_in %>
<a href="...">Log off</a>
<% end %>
<more_html_here>
The output of the Julia code is inserted into the page - the problem is, if ! is_logged_in
the if
will return nothing
which ends up as "nothing"
.
Of course I could force the developer to always do if ... else ...
but that’s really ugly.
Or I could define an if()
function which in the form if(f::Function, condition::Bool)
which would end up as
<% if(is_logged_in) do %>
...
<% end %>
That’s probably acceptable though not equally nice - nor do I like limiting user’s options.
You should just have your own wrapper function that turns nothing
into empty string/ignore it instead of overwriting a base function and breaks every other packages.
The normal approach to this in Julia would be to define your own method, and use dispatch to special-case particular types. For example:
templateoutput(io, x) = println(io, x)
templateoutput(io, ::Void) = nothing
function templateoutput(io, X::AbstractVector)
for x in X
templateoutput(io, x)
end
end
Thanks for your help, much appreciated – I ended up defining my own function, no messing around with Base
.