Description
(This is a Julia 2.0 proposal, as it is breaking. It is surely not novel, but I don't see another such issue open.) We currently treat outer constructors and convert
method definitions as any other generic function, but there's an argument to be made that they should be special-cased by requiring that they always return an object of the stated type. Motivations:
- At present, it's essentially a bug to write fallback methods like this:
foo(x::Int) = 1
foo(x) = foo(Int(x))
Why? Because someone might define
struct Foo end
Int(f::Foo) = f
in which case you get
julia> foo(Foo())
ERROR: StackOverflowError:
Stacktrace:
[1] foo(x::Foo) (repeats 79984 times)
@ Main ./REPL[3]:1
and StackOverflowErrors are to be avoided: they can take forever to resolve in some cases, they can at least in principle trash your session, etc.
The only good way to write such a fallback method is
foo(x) = foo(Int(x)::Int)
but I will bet that the vast majority of developers do not know this or bother with it, and probably everyone finds it ugly and annoying.
Some might complain about idempotent operators and involutions, for example one might be tempted to define
struct Not
x
end
Not(x::Not) = x.x # just strip the `Not` wrapper rather than returning `Not(Not(5))`
But a solution is to separately introduce not
from Not
, and allow this behavior for not
but not Not
(Not
must always wrap in another Not
layer).
- In addition to surprising the developer, failing to enforce this is much harder on inference and the compiler, sometimes with dramatically increased risk of invalidation. Consider the case of Expr constructor sometimes returns a Symbol, Bool, etc julia-vscode/CSTParser.jl#308. For the implementation in https://github.com/julia-vscode/CSTParser.jl/blob/778212a77d2977b94a531240877eacff00700111/src/conversion.jl#L133-L203 it is impossible to predict the outcome. For certain package combinations with the SciML ecosystem, this constructor alone is responsible for thousands of invalidations. Such problems can only be detected by devoted and sophisticated sleuths, but designing the language to be bullet-proof against this solves it neatly with very little cost (when you want to return something different, just check before you call the constructor, not after).