Hunting Bang! Function Bugs


#1

Apologies for this long description with question. I tried to narrow it down as much as I could.

I find it difficult to think well in terms of passing contents. Worse, I am not sure how to debug this well. From a complex and much longer code set, I narrowed down my bug, and my debugging-method, to the following illustration:

mutable struct Point2; x::Int; f::Int; end#struct

################ my custom-rolled way of figuring out where exactly I no longer return the correct thing

gvec= Vector{Point2}(3)  ## storage space

function returncheckin( ps::Vector{Point2}, s::String="" )
    info("$s -- want to bang-change external data to $(ps)")
    global gvec= deepcopy(ps)
end#function##

function returncheckback( ps::Vector{Point2}, s::String="" )
    allx(ps::Vector{Point2})::Vector = [ ps[i].x for i=1:length(ps) ]
    global gvec
    info("$s -- want to check that my post-function data contents are still changed to $gvec")
    @assert( allx( ps ) == allx( gvec ), "\n\tNon-Matching Ret Passers:\n\t\t$ps\n\tvs\t$gvec\n\n" )
end#function


################ my bug example

function manglemydata!( ps::Vector{Point2} )
    dump(ps)
    sort!( ps, by= p -> p.x )   ## does not change the contents inside ps, but ps itself
    dump(ps)  ## I think this ps is not the same storage location as the original ps
    returncheckin( ps )
end#function


left= Point2(+26,+41)
mid= Point2(-4,+25)
right= Point2(+16,+1721)

manglemydata!( [ left , mid , right  ] )
returncheckback( [ left, mid, right ] )

info("ok")

ok. sigh. I now understand that the problem was that the sort! function does not sort the contents of ps, but probably replaces ps…or something like it. (Subtle? Maybe.)

if julia had a keyword like const or immutable that I could place onto the function formal parameters (here, ps) that would flag changes to ps, this sort of bug would be much easier to spot. but julia does not.

so, what is a good practice to recognize such problems early on? the best I could come up with was my returncheckin() and returncheckback(). ugly, handrolled, specific. and in this case, my lifesaver…after a few hours of staring at code that bombed somewhere else because of it. the dump output wasn’t really that useful, because it did not tell me that the storage location of ps had changed.

but it’s not about this particular bug of mine. it’s about something else.

I am wondering whether the best recommendation to julia newbies is to stay clear of writing bang functions (except trivial ones), deepcopy arguments on entry, and pass back all changes as tuples. but that seems like a step back from C/C++, not a step forward.

has something been done (or can something be done) to make such reference bug hunting simpler?

apologies for the long description here…

regards, /iaw


#2

What is the problem? The ps's content should have been mutated. The content of the contents (i.e. the content of the objects in ps are not).

From the way you are talking, it seems that you’ve just got a very deep misunderstanding of objects and variables in julia since you are thinking them as c++ objects and variables. In C++ objects are their storage, which can be variables, fields, array element etc. In julia (and basically ALL scripting languages), objects are just object. Variables, fields, array elements are just how these objects are referenced. In C++ assigning to variables, fields etc is a mutation of the object stored in there. However, in julia (and other scripting languages) it is just a change of reference (point to a different object) and NEVER has any effect on the object. (Or to summarize it, operator= does not make any sense semantically in julia.)


#3

And definitely no.

Also note that this problem is more or less specific to C and especially C++ programmers (I don’t know what go/rust etc is like but I assume those users have either used C/C++ or have seen enough languages to not have this problem). Some very experienced MATLAB users that haven’t used anything else could also have a slightly different version of it.


#4

Maybe an analogy to sorting a vector of integers is helpful:

mutable struct Point2
    x::Int
    f::Int
end

manglemydata!(is::Vector{Int}) = sort!(is)
manglemydata!(ps::Vector{Point2}) = sort!(ps, by=p->p.x)

ints = [ 3, 1, 2 ]
manglemydata!(ints)

left = Point2(+26,+41)
mid = Point2(-4,+25)
right = Point2(+16,+1721)

points = [ left, mid, right ]
manglemydata!(points)

#5

Maybe I don’t understand what do you mean but I think your assumption is mistaken and this is (your) problem:

julia> [ left , mid , right  ] === [ left , mid , right  ]
false

so

manglemydata!( [ left , mid , right  ] )  # parameter here is in different object 
returncheckback( [ left, mid, right ] )   # than parameter here

So you compare different objects not because sort! change one behind the curtain but because you call functions with different parameters.

And everything is OK here when we use same object for sort and for check:

julia> points = [ left, mid, right ]
3-element Array{Point2,1}:
 Point2(26, 41)  
 Point2(-4, 25)  
 Point2(16, 1721)

julia> manglemydata!( points)
3-element Array{Point2,1}:
 Point2(-4, 25)  
 Point2(16, 1721)
 Point2(26, 41)  

julia> returncheckback( points)
INFO:  -- want to check that my post-function data contents are still changed to Point2[Point2(-4, 25), Point2(16, 1721), Point2(26, 41)]

julia>