I'm not sure what exactly is done in a "static check," but if it means that the code is analyzed to check if any assignments are performed I guess that would give a (small?) performance hit.
How are immutable types ensured to not mutate? Perhaps the same check(s) could be done for arguments with intent/mode 'in'?
julia> const x = 11julia> x = 2Warning: redefining constant x2
julia> x = "foo"ERROR: invalid redefinition of constant x
Although I agree competely on the decision to use call-by-reference model as default, I think it would be really nice if Julia could provide a way to pass-by-value as well (as explicit local copies sometimes can cause confusion).
On Tue, Jun 18, 2013 at 1:29 PM, Espen Myklebust <sykl...@gmail.com> wrote:
I'm not sure what exactly is done in a "static check," but if it means that the code is analyzed to check if any assignments are performed I guess that would give a (small?) performance hit.
Adding "intent" would multiply all types by three, adding implicit read-only and write-only variant of each type.
People would start getting subtle errors for calling functions on values in some contexts but not others. Seemingly innocuous changes in certain parts of code would suddenly cause problems in apparently unrelated code.
How would intent annotations interact with multiple dispatch? What if one method of a generic function uses an argument as read-only while another method uses it as write-only? Can you call that function on a read-only value? Can you call it on a write-only value? A read-write value? Does it depend on which method actually gets called? Or does dispatch apply to intent variants? Can you have methods that are distinguishable only by the intent of some argument and which one applies is chosen that way?
How are immutable types ensured to not mutate? Perhaps the same check(s) could be done for arguments with intent/mode 'in'?Again, this is a completely different thing: const applies to bindings, not values. Since bindings are limited to a given scope, they're easier to reason about for both compilers and programmers. Currently, only global constness is even paid any attention to – and we don't even enforce it! You can redefine a const global and you'll just get a warning:
The const declaration is really about letting the compiler generate efficient code.
Although I agree competely on the decision to use call-by-reference model as default, I think it would be really nice if Julia could provide a way to pass-by-value as well (as explicit local copies sometimes can cause confusion).Again, this would be a massive language complication. The Julian approach is to pick one good way of doing things and stick with it – and make sure it's really great. There are *only* dynamic semantics in the language, but you get static performance most of the time. There is only one dispatch mechanism but it's very powerful. Similarly, there are only reference semantics, no value semantics, but since we support immutable types, you can get the advantages of value semantics without needing to switch mental models based on how a type is declared. And you don't need to have two parallel types systems with incompatible semantics like Java and C# do.
In a procedure (Fortran has two types; subroutines and functions) you can declare the "intent" of _dummy_ arguments as "in", "out" or "inout". This has *nothing* to do with the _actual_ arguments passed to the procedure in a call. No variables are declared with "intent", only dummy arguments.
The Fortran standard does not specify how things should be implemented, but it is my impression that most compilers check that the intents of procedure arguments are obeyed when processing the source code (in the compiler front-end) and it does not necessarily have any effect on the resulting object/machine code. It is there for aiding the programmer convey to the compiler exactly what he intends to do with the passed arguments, but because of this it can help the comipler optimize the code better in which case the resulting code of course may be different than without using it.
People would start getting subtle errors for calling functions on values in some contexts but not others. Seemingly innocuous changes in certain parts of code would suddenly cause problems in apparently unrelated code.
In my experience it is precisely the opposite (probably due your understanding of the feature being different from how it works in Fortran). The main reason that I asked if it is possible to declare "intent" in Julia is that it helps me (or rather the compiler) catch errors. It is not uncommon, especially when coming back to a code after some time, that I try to use variables in a manner that is incompatible with how I initially planned the function. When the compiler then complains about this it is usually a good thing as I then avoid possible hard to find errors due to procedure side effects.
How would intent annotations interact with multiple dispatch? What if one method of a generic function uses an argument as read-only while another method uses it as write-only? Can you call that function on a read-only value? Can you call it on a write-only value? A read-write value? Does it depend on which method actually gets called? Or does dispatch apply to intent variants? Can you have methods that are distinguishable only by the intent of some argument and which one applies is chosen that way?
This is of no concern with the above sketched mechanism since there is no dependence on the actual arguments. Of course a good compiler would complain if you provide a parameter (a Fortran constant) for an argument that the procedure has write access for (intent "out" or "inout"), but I must say I prefer a compiler error over a run-time error.
abstract Abstracttype Concrete1 <: Abstract field endtype Concrete2 <: Abstract field endfoo(in x::Abstract) = bar(x)bar(out x::Concrete1) = (x.field = "hello"; return)bar(in x::Concrete2) = return
How are immutable types ensured to not mutate? Perhaps the same check(s) could be done for arguments with intent/mode 'in'?Again, this is a completely different thing: const applies to bindings, not values. Since bindings are limited to a given scope, they're easier to reason about for both compilers and programmers. Currently, only global constness is even paid any attention to – and we don't even enforce it! You can redefine a const global and you'll just get a warning:
This could be the only thing that would happen to an "intent in" argument also (and the same if one would try evaluate an "intent out" variable), at least to begin with (as long as you don't enforce const either).A side question: I was asking about immutable composite types which are declared with the immutable keyword. Is that equivalent to an instance of a "regular" composite type declared with const?
julia> const a = [1]1-element Int64 Array:1julia> a = "hello"ERROR: invalid redefinition of constant ajulia> a1-element Int64 Array:1julia> a[1] = 22julia> a1-element Int64 Array:2
The const declaration is really about letting the compiler generate efficient code.That is precisely what I think can be done with providing argument intent; an argument could be treated as a const within the function allthough it's really not.
Although I agree competely on the decision to use call-by-reference model as default, I think it would be really nice if Julia could provide a way to pass-by-value as well (as explicit local copies sometimes can cause confusion).Again, this would be a massive language complication. The Julian approach is to pick one good way of doing things and stick with it – and make sure it's really great. There are *only* dynamic semantics in the language, but you get static performance most of the time. There is only one dispatch mechanism but it's very powerful. Similarly, there are only reference semantics, no value semantics, but since we support immutable types, you can get the advantages of value semantics without needing to switch mental models based on how a type is declared. And you don't need to have two parallel types systems with incompatible semantics like Java and C# do.I never programmed either Java or C#, but it sure sounds like an laborious approach... Again, I can only say what is the case in Fortran which, being a rather simplistic(in a good sense) language in this respect, doesn't make a programmes life harder by such intricate constructs. When a (dummy) argument is declared with the "value" attribute the compiler simply makes a local copy of the variable to the procedure scope, just as Matlab (and I suppose C) does. The reason I suggested it was that i thought this would be relatively straight forward to implement; it could probably be done in a "macro-style" (not a reference to Julia macros, as I havent studied them yet) where code for copying the actual argument is inserted and the reference pointer changed to the copy instead of the actual argument.
Perhaps you ask yourself why I'm not just using Fortran. The thing is that I'm also using Matlab a lot, and it is for that part of my work I'm looking into switching to Julia, and since it seems like the language is still in an "early stage" of development (?) I thought that perhaps it would be possible to have some of the features that I find to be very nice about Fortran in Julia... I get that saying no is imporant, but I just wanted to try to clear up what seems to me being a misunderstanding of concepts.
On Wed, Jun 19, 2013 at 10:49 AM, Espen Myklebust <sykl...@gmail.com> wrote:In a procedure (Fortran has two types; subroutines and functions) you can declare the "intent" of _dummy_ arguments as "in", "out" or "inout". This has *nothing* to do with the _actual_ arguments passed to the procedure in a call. No variables are declared with "intent", only dummy arguments.I'm not sure what dummy arguments means here. They're never used?
The Fortran standard does not specify how things should be implemented, but it is my impression that most compilers check that the intents of procedure arguments are obeyed when processing the source code (in the compiler front-end) and it does not necessarily have any effect on the resulting object/machine code. It is there for aiding the programmer convey to the compiler exactly what he intends to do with the passed arguments, but because of this it can help the comipler optimize the code better in which case the resulting code of course may be different than without using it.In a dynamic language, you cannot generally statically check everything. I think this complicates this issue more than you may be realizing.
People would start getting subtle errors for calling functions on values in some contexts but not others. Seemingly innocuous changes in certain parts of code would suddenly cause problems in apparently unrelated code.
In my experience it is precisely the opposite (probably due your understanding of the feature being different from how it works in Fortran). The main reason that I asked if it is possible to declare "intent" in Julia is that it helps me (or rather the compiler) catch errors. It is not uncommon, especially when coming back to a code after some time, that I try to use variables in a manner that is incompatible with how I initially planned the function. When the compiler then complains about this it is usually a good thing as I then avoid possible hard to find errors due to procedure side effects.I should clarify that what I meant is that people would get subtle *compiler* errors that would be annoying and hard to figure out. The obvious response is that when that happens they would otherwise be getting subtle bugs that would be even harder to figure out. While that's sometimes the case, there are a lot of situations where you know you can appear to violate read/write "rules" but actually be just fine.
How would intent annotations interact with multiple dispatch? What if one method of a generic function uses an argument as read-only while another method uses it as write-only? Can you call that function on a read-only value? Can you call it on a write-only value? A read-write value? Does it depend on which method actually gets called? Or does dispatch apply to intent variants? Can you have methods that are distinguishable only by the intent of some argument and which one applies is chosen that way?
This is of no concern with the above sketched mechanism since there is no dependence on the actual arguments. Of course a good compiler would complain if you provide a parameter (a Fortran constant) for an argument that the procedure has write access for (intent "out" or "inout"), but I must say I prefer a compiler error over a run-time error.I can't really tell what the sketched mechanism is, but I'm pretty sure this is not possible to sweep this issue under the carpet like this unless your mechanism is purely for documentation and not checked at all. Example:
abstract Abstracttype Concrete1 <: Abstract field endtype Concrete2 <: Abstract field endfoo(in x::Abstract) = bar(x)bar(out x::Concrete1) = (x.field = "hello"; return)bar(in x::Concrete2) = returnIs this an error or not? You certainly can't tell at compile time. It might be possible to make this work, but it's not simple.
How are immutable types ensured to not mutate? Perhaps the same check(s) could be done for arguments with intent/mode 'in'?Again, this is a completely different thing: const applies to bindings, not values. Since bindings are limited to a given scope, they're easier to reason about for both compilers and programmers. Currently, only global constness is even paid any attention to – and we don't even enforce it! You can redefine a const global and you'll just get a warning:
This could be the only thing that would happen to an "intent in" argument also (and the same if one would try evaluate an "intent out" variable), at least to begin with (as long as you don't enforce const either).A side question: I was asking about immutable composite types which are declared with the immutable keyword. Is that equivalent to an instance of a "regular" composite type declared with const?I think you have a deep misapprehension about const and mutability: const only affects bindings and is completely orthogonal to object mutability. Example:julia> const a = [1]1-element Int64 Array:1julia> a = "hello"ERROR: invalid redefinition of constant ajulia> a1-element Int64 Array:1julia> a[1] = 22julia> a1-element Int64 Array:2Making a function parameter const just means you can't reassign the parameter name in the body of the function. Enforcing this is pretty much useless since reassigning the parameter name has no effect on the caller anyway. Making a parameter read-only is about preventing *mutation* – i.e. things of the type `a[1] = 2`, not things of the type `a = "hello"`.
The const declaration is really about letting the compiler generate efficient code.That is precisely what I think can be done with providing argument intent; an argument could be treated as a const within the function allthough it's really not.Constness only helps with globals. It is not at all helpful with locals. There are a few ways in which knowing whether an object is read-only or write-only could help optimization (knowing that a 1-d array won't get resized because it's read-only comes to mind), but there aren't a whole lot of them, and there are other ways to get that information.
Although I agree competely on the decision to use call-by-reference model as default, I think it would be really nice if Julia could provide a way to pass-by-value as well (as explicit local copies sometimes can cause confusion).Again, this would be a massive language complication. The Julian approach is to pick one good way of doing things and stick with it – and make sure it's really great. There are *only* dynamic semantics in the language, but you get static performance most of the time. There is only one dispatch mechanism but it's very powerful. Similarly, there are only reference semantics, no value semantics, but since we support immutable types, you can get the advantages of value semantics without needing to switch mental models based on how a type is declared. And you don't need to have two parallel types systems with incompatible semantics like Java and C# do.I never programmed either Java or C#, but it sure sounds like an laborious approach... Again, I can only say what is the case in Fortran which, being a rather simplistic(in a good sense) language in this respect, doesn't make a programmes life harder by such intricate constructs. When a (dummy) argument is declared with the "value" attribute the compiler simply makes a local copy of the variable to the procedure scope, just as Matlab (and I suppose C) does. The reason I suggested it was that i thought this would be relatively straight forward to implement; it could probably be done in a "macro-style" (not a reference to Julia macros, as I havent studied them yet) where code for copying the actual argument is inserted and the reference pointer changed to the copy instead of the actual argument.This sounds like you're talking about just calling copy immediately inside of a function. That's a reasonable thing to do and something that a number of Julia functions do in fact do. I'm not sure what "macro style" means here.
Perhaps you ask yourself why I'm not just using Fortran. The thing is that I'm also using Matlab a lot, and it is for that part of my work I'm looking into switching to Julia, and since it seems like the language is still in an "early stage" of development (?) I thought that perhaps it would be possible to have some of the features that I find to be very nice about Fortran in Julia... I get that saying no is imporant, but I just wanted to try to clear up what seems to me being a misunderstanding of concepts.Great to have you trying the language out. In general, I'll just say that Fortran and Julia are very different beasts. It's generally going to be pretty hard to just "move" features from a static, compiled language with zero polymorphism to a language whose core paradigm is dynamic multiple dispatch on generic types – this is about as polymorphic and dynamic as it gets. You're much more likely to "import" features from languages like Matlab, Python or Lisp easily (we have a lot of them already).
Values are passed and assigned by reference. If a function modifies an array, the changes will be visible in the caller.
Relying on other people having the same understanding of this term can evidently cause some confusion. If the above line from the documentation is correct though, my previous points must be valid for arrays at least..?