1
votes

Does anyone know the reasons why Julia chose a design of functions where the parameters given as inputs cannot be modified?  This requires, if we want to use it anyway, to go through a very artificial process, by representing these data in the form of a ridiculous single element table.

Ada, which had the same kind of limitation, abandoned it in its 2012 redesign to the great satisfaction of its users. A small keyword (like out in Ada) could very well indicate that the possibility of keeping the modifications of a parameter at the output is required.

2
Though you ask why, the what in your question isn't quite right. See Bogumił's answer below.rickhg12hs
You worry because I consider Julia as a first class person making choices for its own design ? (smile)Schemer
I only worry that someone else might read your loaded question and think all the details are correct. :)rickhg12hs
Sorry but my question is a first class person, and she does not let me control all its details !! (smile)Schemer
Questions are powerful. True. :smile:rickhg12hs

2 Answers

2
votes

From my experience in Julia it is useful to understand the difference between a value and a binding.

Values

Each value in Julia has a concrete type and location in memory. Value can be mutable or immutable. In particular when you define your own composite type you can decide if objects of this type should be mutable (mutable struct) or immutable (struct).

Of course Julia has in-built types and some of them are mutable (e.g. arrays) and other are immutable (e.g. numbers, strings). Of course there are design trade-offs between them. From my perspective two major benefits of immutable values are:

  1. if a compiler works with immutable values it can perform many optimizations to speed up code;
  2. a user is can be sure that passing an immutable to a function will not change it and such encapsulation can simplify code analysis.

However, in particular, if you want to wrap an immutable value in a mutable wrapper a standard way to do it is to use Ref like this:

julia> x = Ref(1)
Base.RefValue{Int64}(1)

julia> x[]
1

julia> x[] = 10
10

julia> x
Base.RefValue{Int64}(10)

julia> x[]
10

You can pass such values to a function and modify them inside. Of course Ref introduces a different type so method implementation has to be a bit different.

Variables

A variable is a name bound to a value. In general, except for some special cases like:

  1. rebinding a variable from module A in module B;
  2. redefining some constants, e.g. trying to reassign a function name with a non-function value;
  3. rebinding a variable that has a specified type of allowed values with a value that cannot be converted to this type;

you can rebind a variable to point to any value you wish. Rebinding is performed most of the time using = or some special constructs (like in for, let or catch statements).

Now - getting to the point - function is passed a value not a binding. You can modify a binding of a function parameter (in other words: you can rebind a value that a parameter is pointing to), but this parameter is a fresh variable whose scope lies inside a function.

If, for instance, we wanted a call like:

x = 10
f(x)

change a binding of variable x it is impossible because f does not even know of existence of x. It only gets passed its value. In particular - as I have noted above - adding such a functionality would break the rule that module A cannot rebind variables form module B, as f might be defined in a module different than where x is defined.

What to do

Actually it is easy enough to work without this feature from my experience:

  1. What I typically do is simply return a value from a function that I assign to a variable. In Julia it is very easy because of tuple unpacking syntax like e.g. x,y,z = f(x,y,z), where f can be defined e.g. as f(x,y,z) = 2x,3y,4z;
  2. You can use macros which get expanded before code execution and thus can have an effect modifying a binding of a variable, e.g. macro plusone(x) return esc(:($x = $x+1)) end and now writing y=100; @plusone(y) will change the binding of y;
  3. Finally you can use Ref as discussed above (or any other mutable wrapper - as you have noted in your question).
0
votes

"Does anyone know the reasons why Julia chose a design of functions where the parameters given as inputs cannot be modified?" asked by Schemer

Your question is wrong because you assume the wrong things.

  1. Parameters are variables

When you pass things to a function, often those things are values and not variables.

for example:

function double(x::Int64)
  2 * x
end

Now what happens when you call it using

double(4)

What is the point of the function modifying it's parameter x , it's pointless. Furthermore the function has no idea how it is called.

Furthermore, Julia is built for speed.

A function that modifies its parameter will be hard to optimise because it causes side effects. A side effect is when a procedure/function changes objects/things outside of it's scope.

If a function does not modifies a variable that is part of its calling parameter then you can be safe knowing.

  1. the variable will not have its value changed
  2. the result of the function can be optimised to a constant
  3. not calling the function will not break the program's behaviour

Those above three factors are what makes FUNCTIONAL language fast and NON FUNCTIONAL language slow.

Furthermore when you move into Parallel programming or Multi Threaded programming, you absolutely DO NOT WANT a variable having it's value changed without you (The programmer) knowing about it.

"How would you implement with your proposed macro, the function F(x) which returns a boolean value and modifies c by c:= c + 1. F can be used in the following piece of Ada code : c:= 0; While F(c) Loop ... End Loop;" asked by Schemer

I would write

function F(x)
  boolean_result = perform_some_logic()
  return (boolean_result,x+1)
end

flag = true
c = 0
(flag,c) = F(c)
while flag
  do_stuff()
  (flag,c) = F(c)
end

"Unfortunately no, because, and I should have said that, c has to take again the value 0 when F return the value False (c increases as long the Loop lives and return to 0 when it dies). " said Schemer

Then I would write

function F(x)
  boolean_result = perform_some_logic()
  if boolean_result == true
      return (true,x+1)
  else
      return (false,0)
  end
end

flag = true
c = 0
(flag,c) = F(c)
while flag
  do_stuff()
  (flag,c) = F(c)
end