The problem lies in detecting (or, rather, teaching the compiler to detect) 
if a function - or any of the functions it calls - mutates an argument; 
this is a fairly difficult problem.

If you take a look at the Julia standard library, you'll see that a lot of 
functions have both mutating and non-mutating variants, usually implemented 
with the non-mutating variant as a thin wrapper that just copies the input 
arguments and calls the mutating variant:

function foo(a, b)
    foo!(copy(a), copy(b))
end

function foo!(a, b)
   # do whatever, including mutating a and b
end

Of course, one could argue that the opposite (wrapping the non-mutating 
variant with a mutating variant) would be just the same, but copying 
explicitly is easier to do with fewer language constructs. We can agree (I 
hope) that there is merit in allowing pass-by-reference behavior *sometimes*, 
since it does allow for (sometimes very significant) performance gains, so 
if we don't have it by default we have to figure out a way to specify that 
it should be by reference. And by the way, should that be done by the 
caller, or by the callee? For example, consider foo! above - either, we 
could imagine a keyword ref that specifies pass-by-reference (like & in 
C++):

foo!(ref a, ref b)
    # do whatever
end

Now, however, we still have the problem that the caller doesn't necessarily 
know that foo! will mutate its arguments (it has the bang, but that's just 
convention...), so it could be equally confusing to a new user. We could 
instead demand that pass-by-reference was specified at the call site:

a = [1:4]
b = [5:7]
foo!(ref a, ref b)

but that would litter our code with ref keywords everywhere in order to not 
lose a lot of performance from unnecessary copying (exactly the problem 
that C++ suffers from). Additionally, some functions might just not work 
without pass-by-reference - take, for instance, A_mul_B! from the linalg 
library - it's signature is A_mul_B!(C, A, B), and it multiplies the 
matrices A and B and stores the result in the (pre-allocated) array C. With 
the ref construct, that would have to be A_mul_B!(ref C, A, B) in order to 
work at all - but what should happen if you forget ref, and call 
A_mul_B!(C, A, B)? By using pass-by-reference everywhere, you never have to 
reason about this - it behaves the same every time. If you need a copy, 
make one - and since you make one explicitly, you may notice optimization 
possibilities that you wouldn't have thought of otherwise.

As you can see, although there are of course back-sides of the 
pass-by-reference convention, there are sound arguments in favor of it 
also. It is a little different than how some other languages work, but 
"different" and "dangerous" are not synonyms :) It is well documented, and 
- as you've noticed - we learn from each other. Give it a chance, and 
within a couple of weeks of writing Julia code both the argument passing 
behavior, the bang convention and a bunch of other stuff will feel as 
natural as any other way of doing it =)

// T

On Friday, December 26, 2014 10:04:57 PM UTC+1, Páll Haraldsson wrote:
>
>
> I wander if the convention should have been made the other way as not all 
> will know it at first..
>
> If you do not want your arguments touched then you can trivially make a 
> non-mutating wrapper using the similar function. It seems that could have 
> been automated.. Maybe you can with a macro?
>
> Would it be easy to give a warning or an error for functions that are 
> mutating? Or really, if it can be detected then shouldn't the function be 
> renamed to the ! variant and a wrapper be made?
>
> Best regards,
> Palli.
>
> On Friday, December 26, 2014 8:42:18 PM UTC, Tomas Lycken wrote:
>>
>> Glad to be of service! =)
>>
>> Languages treat both aliasing and argument passing differently, so it 
>> really just takes a little while to experiment and see how this language 
>> plays out what you're trying to do. For what it's worth, Julia also has 
>> a style convention to end all method names with a bang ("!") if they mutate 
>> their arguments. So with your first example, the function could have been 
>> named square!(arg), and then users of that function would be immediately 
>> aware that arg would be changed. This convention isn't really enforced in 
>> any way (except code review) - it's perfectly valid Julia to mutate 
>> function arguments even if the function name doesn't end with ! - but the 
>> core libraries as well as most packages do follow it.
>>
>> Happy coding!
>>
>> // Tomas
>>
>> On Friday, December 26, 2014 9:33:12 PM UTC+1, Bradley Setzler wrote:
>>>
>>> Thanks Tomas, the similar command will be very useful in avoiding this 
>>> issue. 
>>>
>>> Thank you also for a thoughtful and informative response, Tomas.
>>>
>>> Bradley
>>>
>>> PS - For what it's worth, R does not have this behavior.
>>>
>>>
>>>
>>> On Friday, December 26, 2014 2:05:44 PM UTC-6, Tomas Lycken wrote:
>>>>
>>>> The main reason is performance; passing and aliasing arrays this way 
>>>> (those are two different concepts, which work together to make this 
>>>> particular example a little confusing) allow for writing code that is as 
>>>> fast as possible, by leaving the coder in control of when a copy is made 
>>>> and when it is not. A more idiomatic way of writing your square function, 
>>>> which would do what you expect, would be
>>>>
>>>> ```
>>>> function square(A)
>>>>     A2 = similar(A)
>>>>     for i = 1:length(A)
>>>>         A2[i] = A[i]^2
>>>>     end
>>>>     A2
>>>> end
>>>> ```
>>>>
>>>> The funciton `similar` allocates new memory for an array of similar 
>>>> size and type as `A`. You could also have left your entire method untouch 
>>>> except setting `inner_var = copy(arg)`, but this would have meant both 
>>>> reading and writing more to memory than you need to. As you see, Julia's 
>>>> behavior allows you to write more performant code than what you'd do 
>>>> otherwise.
>>>>
>>>> // Tomas
>>>>
>>>> On Friday, December 26, 2014 8:58:04 PM UTC+1, Bradley Setzler wrote:
>>>>>
>>>>> Why would you want this behavior? How could you possibly benefit from 
>>>>> modifying X anytime you modify Y just because Y=X initially? If I wanted 
>>>>> to 
>>>>> modify X, I would modify X itself, not Y. 
>>>>>
>>>>> Bradley
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> On Friday, December 26, 2014 1:53:12 PM UTC-6, John Myles White wrote:
>>>>>>
>>>>>> This is aliasing. Almost all languages allow this.
>>>>>>
>>>>>>  -- John
>>>>>>
>>>>>> Sent from my iPhone
>>>>>>
>>>>>> On Dec 26, 2014, at 2:49 PM, Bradley Setzler <[email protected]> 
>>>>>> wrote:
>>>>>>
>>>>>> Hi,
>>>>>>
>>>>>> I cannot explain this behavior. I apply a function to a variable in 
>>>>>> the workspace, the function initializes its local variable at the 
>>>>>> workspace 
>>>>>> variable, then modifies the local variable and produces the desired 
>>>>>> output. 
>>>>>> However, it turns out the Julia modifies both the local and workspace 
>>>>>> variable with each operation on the local variable. Only the local 
>>>>>> variable 
>>>>>> is supposed to be modified. 
>>>>>>
>>>>>> *This is very dangerous behavior, as Julia is modifying the data 
>>>>>> itself between performing operations on the data; the data itself is 
>>>>>> supposed to remain fixed between operations on it.*
>>>>>>
>>>>>> *Minimal working example:*
>>>>>>
>>>>>> data=[1,2,3]
>>>>>> function square(arg)
>>>>>>     inner_var = arg
>>>>>>     for i=1:length(inner_var)
>>>>>>         inner_var[i] = inner_var[i]^2
>>>>>>     end
>>>>>>     return inner_var
>>>>>> end
>>>>>> output=square(data)
>>>>>>
>>>>>> julia> print(data)
>>>>>>
>>>>>> [1,4,9]
>>>>>>
>>>>>> The data has been squared due to the local variable, which was 
>>>>>> initialized at the data values, being squared. Now, if i wish to apply a 
>>>>>> different function to the data, the result will be incorrect because the 
>>>>>> data has been modified unintentionally.
>>>>>>
>>>>>> How long has Julia been doing this? Was this behavior intentional?
>>>>>> Bradley
>>>>>>
>>>>>>
>>>>>>

Reply via email to