Welcome! Yes, that’s an unusually bad error message.
The trailing commas there are your problem; they’re mixing up all your assignments in the update function (the mixup is making Julia think you’re defining your own + method). Commas are only used to separate function arguments and list elements, not lines of code.
I’d like to make a proposal for Julia 2.0: it should be illegal to define named functions except at the outermost level of the containing function (or module). This will prevent many baffling error messages, and it will also prevent new users from making mistakes like:
function outer(x)
if x > 0
function nested()
3
end
else
function nested()
4
end
end
end
You definitely don’t want that restriction at a module, conditional method definitions there are fine and useful. You probably don’t want that restriction in a function either because not every block is conditional and will work fine e.g. let inner=3; foo() = inner end, and loops for capturing variables don’t override methods so those are fine e.g. for inner in 1:3 foo() = inner end. Basically you’re fine if you don’t write multiple nested definitions for the same method signature (prints warning), and you can’t really conditionally define any nested method even if it’s just for capturing variables (doesn’t print warning). On a practical level, it is still easier to write more complicated method headers in the named function syntax than in the anonymous function syntax. I don’t think this work needs to wait for 2.0, issue 15602 already exists for this and is where serious thought-out proposals should go. Turns out figuring out when nested method definitions are wrong is not so clear-cut.
I agree with you that at the module level, it makes sense to allow conditional named-function definition. So I retract that part of my proposal.
However, I am not convinced that defining named functions inside nesting levels that are inside another function is a useful feature of the language. It is possible to accomplish the same goal using anonymous functions (e.g. let inner=3; foo = () -> inner; end) without as many pitfalls. Thanks for pointing out the github issue. Here are other issues about this. All these issues would go away if nested definitions of named functions were banned.
I like local functions. They’re useful enough that they were added to C# several years ago.
Sometimes it’s the most logical, easiest way to break down a complex function into parts, without polluting the namespace with functions that don’t matter anywhere else.
#17730 is invalid syntax in 1.10 now
#15483 is tricky but the issue is about how it’s possible to accidentally define a function with an operator, not where that definition is located. In that case, I think it would make sense to not allow operator-like function definitions inside other functions.
Just to be clear: I am not opposed to local functions. Rather, I am opposed to named local functions that lie inside a syntax nesting that is inside the outer function. In other words, I am OK with both of the following
function outer()
function inner()
end
end
and
function outer(a)
f = a > 0 ? () -> 4 : () -> 5
end
but I believe that code like the OP’s that redefined + should be banned.
It seems like a footgun that it’s possible to define methods using infix notation. I suppose it would be breaking to remove (maybe only a “minor” change? do people really use that pattern on purpose?), but it doesn’t seem very useful because (from the 90 seconds I’ve spent on it) one can’t add dispatch types to infix definitions. So either you’re defining an available-but-unused operator on ::Any inputs (fine, although maybe not that common and call syntax is still available), pirating (shame on you), or making a mistake. EDIT: it looks like you can declare dispatch types, although it will still shadow the Base operator if you don’t import <the_operator> or qualify it as Base.:<the_operator>.
EDIT: It looks like there’s been some discussion on this topic happening for a while now, e.g. #15483, with some in favor and some against (even among influential devs). Unless opinions have shifted to dominantly unfavorable, I think it’d be difficult to change the state of infix definitions.
There was a posting a few years back on this forum about another instance of an errant function definition caused by a statement like this buried deep inside an outer function:
assert(length(v)=n)
The error message produced was something like length(::Vector{...}) is undefined. The line that generated the error message was far away from incorrect assert statement. So it is not just infix function definition that creates the problem.