Yes, but also that
function f(const a)
g(a)
end
give an error if g mutates a, which is not in principle possible in the general case because it’s probably equivalent to the halting problem given Julia’s general purpose dynamism.
Yes, but also that
function f(const a)
g(a)
end
give an error if g mutates a, which is not in principle possible in the general case because it’s probably equivalent to the halting problem given Julia’s general purpose dynamism.
Rust does this kind of thing. Maybe the new Base.infer_effects
could be used for similar purpose.
Also, it’s not clear when this error should show up…
function f(const a)
g(a)
end
## does error happen here?
q = SomeStruct(...)
f(q) ## or here?
struct OtherStruct
...
end
r = OtherStruct(...)
function g(a::OtherStruct)
... mutates state here
end
f(r) # or not above, because g(::SomeStruct) doesn't mutate but here because g(::OtherStruct) does?
or in the worst case:
### 3 years pass
include("additionalfunctionality.jl")
f(NewStruct(...))# here?
Is OK. I will not write again. btw many things.
Could you at least reply to my question above about when the error should occur?
I think this would help me at least. I understand if after that you’d prefer to be done with the topic, but I honestly don’t understand when you want the error.
Given how much activity there’s been and how little progress, I suspect this thread would benefit from a pause.
what error?!? All errors that we discuss are compile time errors both in Julia and C++. Compilation can happen at many times.
We do that by documentation. And, if you are not worried about ways of thwarting it like you claim, then documentation should be enough. The only reason for this to be a construct is that you are worried about people thwarting it.
The error message emitted by Julia. when should Julia emit an error message in the examples I gave above?
Conceptually in C++ compile time is an instant in time. At the point where you compile and link the main.cpp or whatever. At that point in time, the compiler can go into the main function figure out all the functions it calls, figure out all the functions all those functions call, template specialize everything, and compile every function that could every possibly be called.
While its doing that, it could emit all the possible errors. This set is a finite set.
Julia just doesn’t have such a conceptual point in time. What it has is an ongoing computing process where compilation of various things happen at irregular intervals depending on what happens during computation. Hence, compiling Julia code in the general case is equivalent to the halting problem. You can only decide if an error can occur by simulating the run of the code out infinity. That isn’t to say you can’t have decent Linters that help you a bunch, but just that the complexity of deciding on whether an error occurs is of a different type than the complexity of deciding whether const is violated in C++.
Is my bad that developing with hundreeds of developers huge programs with many million of lines of code always had happen that documentation went rapidly out scope and some helpfull beginer did some „improvements” that corrupted de code and costs us in tens of thousand to track and reverse back. It is true that we minimize the damage much better and reverse the changes automatic when that’s happen, but I can say: „The best code is the active enforced code and not the commented code.” This is not my saying is said by hundreeds of pros that are doing large scale software development.
To conclude: The comments are not the optimal answer!
I don’t think you have realized that nobody here is arguing that it’s a bad thing to enforce code safety or some desired behavior. We all want it, there’s a reason why people repeatedly linked JET.jl and talked about linters. We don’t like running into errors only at function calls either, we also want to catch as many errors as possible when the code is parsed, long before we waste any time computing.
It’s just that Julia is not statically typed and is not AOT-compiled. Hypothetically an extra script full of precompile statements (like function calls that are compiled but don’t run) can allow Julia to be compiled AOT in a way, but the disconnect between function calls and methods with abstract type annotations is a fundamental barrier to the manual variable restrictions and comprehensive static analysis you’re proposing. There’s no way to “figure that out in 30 years like C++ did” unless Julia v2 is turned into a statically typed language, which would sacrifice many intentional features or at least make them much more difficult to write. Language design is just full of pros and cons, one feature sabotaging another, you can’t have everything.
Commenters have pointed out several reasons why Julia isn’t designed in a way that makes your desired const
behavior feasible, and they’ve offered the closest features Julia has to offer (immutable vs mutable types), while acknowledging it’s not close to what you want. If you wanted answers to “why I can’t…”, you got them.
Yes, what @Benny said… we’re all in favor of finding bugs! we just acknowledge that solving the halting problem isn’t going to be possible any time soon, so we have to be satisfied with some other lesser thing.
This has been said back and forth, but consider the following situation:
julia> using ReadOnlyArrays
julia> f(x::Int, v::ReadOnlyArray) = v[1] = x
f (generic function with 1 method)
julia> f(1, [1,2,3])
ERROR: MethodError: no method matching f(::Int64, ::Vector{Int64})
...
julia> f(1, ReadOnlyArray([1,2,3]))
ERROR: CanonicalIndexError: setindex! not defined for ReadOnlyArray{Int64, 1, Vector{Int64}}
...
The signature of f
guarantees that no argumente is mutated, because the x
is immutable, and thus by the way Julia passes variables, it wont ever be mutated by the function inner scope. The array cannot be mutated because that function only accepts ReadOnlyArray
types. A function defined like that seems to have all the guarantees you want, and one could (and it can be reasonable in some scenarios) to create a ReadOnlyType
for the types that are relevant to your project to be able to define functions like that.
Calling that function requires using f(1, ReadOnlyArray(v))
where v
is any array, but that can even be seen as a plus in terms of code clarity.
It seems that it is possible to develop a large project increasing the guarantees you require from the function signatures. Wanting that the language in general follows that is harder, because until now there were no strong feelings for that feature (though nobody denies it can be useful and good in many cases).
What people expressed in these forums many times is that auxiliary tools (linters) that check for these and other guarantees are important and desired, that without effectively restricting the features of the language can provide a safer development workflow.
The problem is not about ReadOnlyArrays or AnyArray is about parameters in general.
There are only really arrays and mutable structs that the issue arises at all.
I understand, what I said is that if that feature is absolutely necessary for the development of some specific project it can be implemented. It is less work than it seems, because for isbits types the guarantee is already given, and for arrays the solution already exist. If the project is self contained in some sense, that style is feasible.
OP has already made clear that he has no interest in immutable types, he wants to be able to pass mutable instances in a function but specify no mutation within that function’s runtime. Immutable wrappers are insufficient to mimic that feature: 1) the MethodError
thrown when trying to mutate immutable instances only triggers at function call, OP clearly wants it sooner, 2) immutable wrappers can only be seamlessly substituted in methods if the mutable and immutable versions both share some abstract supertype; for example, a ReadOnlyArray{OffsetArray}
can substitute for OffsetArray
in AbstractArray
methods, but not in OffsetArray
methods, 3) even if OP accepts explicit annotation with immutable types, generic immutable wrappers can’t comprehensively forward nonmutating methods for all mutable parents; ReadOnlyStruct
only handles getproperty
, so you’ll have to implement many more specific ReadOnly___
types, which is way more work than writing cpp_const
.
This is the spirit. I don’t want it soon. I just want to point that this lack of feature will stop Julia to become a big language used for big projects. I’m glad that we arrive to the conclusion that constness of parameters should be used to generate the unique name in the compiler to properly support this step like it is done in other languages used in large projects. I will personally stil use Julia for a while because is amasing even how it is now.
I don’t think this is true. Julia seems to be a language that offers the benefits of C/C++ (i.e., speed) without having to deal with the obscure code like you are talking about. It’s expressiveness and ease of use is particularly attractive to scientists, mathematicians, researchers, novice programmers, etc. It’s attractive to those individuals that do not want to deal with the obscurity of C++. And this has been a huge reason for its fast adoption. Some of these folks might not even understand your described problem here. I, for one, don’t understand why this is an issue, especially because we have ReadOnlyArrays
and you can use type signatures in methods to constrict your arguments. Someone posted an answer above… It’s a solution to your problem but you don’t like it because… its not in the same notation as c++? I don’t think thats a valid criticism.
I want to ask what is your use case in c++? Your 30 years of experience in c++ is doing what? software development? IoT? Game Engines? Scientific Programming?
The soonness that was referred to there had to do with at what point during the Julia read eval compile process would the error message be emitted not how quickly such a functionality would be developed for Julia’s runtime. Benny is pointing out that my contrived example that it takes 3 years for you to get the error message because it doesn’t exist as an error until a new method is created after a long run time or because of whatever other reasons is probably going to be unsatisfactory for your usage.