Hacker News new | ask | show | jobs
by nnm 1372 days ago
Comment on the functional programming and mutation of data. I learnt R before learnt python.

In R

y = c(1, 2, 3)

x = y

# now x is a copy of y

I was surprised that in python

y = [1, 2, 3]

x = y

# now x is y! What, why?

In R, I am used to the fact everything is an expression:

a = if (x > y) { 0 } else { 1 }

2 comments

R has call-by-value semantics. Python has call-by-reference. These are entirely two different programming paradigms.

https://stackoverflow.com/questions/15759117/what-exactly-is...

Python is not call-by-reference. If it were, we would expect 2 to be printed below, but it is not:

  >>> def f(x):
  ...     x = 2
  >>> x = 1
  >>> f(x)
  >>> x
  1
One might refer to r as being referentially transparent, or immutable (I don't know if it is--I am assuming). I would refer to python as using uniform reference semantics - http://metamodular.com/common-lisp-semantics.html
What does that Python mean, by the way? The passing of the value into the function is clear enough, but I have no idea whether f is assigning to the local x, or binding a new one.

If we capture a lambda before "x = 2", and then call it afterward, does it see an x which holds the original argument value or which holds 2?

In Lisps, this stuff is clear. Mostly. Except Common Lisp has some areas where it waffles: in some iteration constructs, implementations can bind a new variable or reuse the same one. Those choices are individually clear, though.

Creating a new one. It isn't a closure, so:

  x = 3
  def f(x): # note the name here
    x = 20
  f(x)
  x # => 3
Similarly, using a different parameter name:

  def g(y):
    x = y
  g(20)
  x # => 3
You have to explicitly mark the `x` in the function to be the global one to reference it:

  def h(y):
    global x
    x = y
  h(10)
  x # => 10
With a lambda it would be a closure:

  i = lambda y : x # throwing away y for fun
  i(3) # => 10
  x = 20
  i("hi") # => 20
No, I mean this:

  def f(x):
    g = lambda : x
    x = 2
    return g()

  f(42)
What is does f(42) return?

Holy fuck, I simply cannot just cut and paste the above into Python as-is, because of the "unexpected indent" which I added so that HN shows that as quote. It's complaining about an unexpected indent.

Kill. Me. Now.

Anywway:

  >>> def f(x):
  ...   g = lambda : x
  ...   x = 2
  ...   return g()
  ... 
  >>> f(42)
  2
So what it looks like is that there is only one x in the function, established by the parameter. This x is captured by the lambda, and is then reassigned the value 2. The lambda retrieves the 2.

For completeness we show that if x is not assignmed, g accesses f's argument value:

  >>> def f(x):
  ...    g = lambda : x
  ...    return g()
  ... 
  >>> f(42)
  42
`f` behaves just like closure. It can even be assigned to a variable.

    def f(x): x = 2 # <- this `x` is the same `x` as in the argument, it can be access via locals() internally

You can even assign `f`. For example, `function = f`.

Python is call by reference. Change my mind. `def f(x): x[0] = 1` will manipulate whatever object you pass to it.

If Python were fully call by reference then:

  def g(y):
    y = 3
  x = 10
  g(x)
  x # => ??
If it's 3 then it's pass by reference here, if it's 10 it's pass by reference. Which is it?

Additionally, for something to be a closure it has to close over something (an environment). What does `f` or `g` close over? Note that they aren't changing any environment, they are "merely" functions, not closures. Python does have closures, but those aren't examples of them.

And being able to assign a function to a variable does not make a closure, or do you think that C has closures because it has function pointers?

> Python is call by reference. Change my mind.

No need to change your entire mind; just an incorrect definition of call-by-reference.

Passing a value which has reference semantics isn't call-by-reference.

Your f receives x by value. That value is a reference into a boxed object. The x[0] = 1 does not change x; it changes the boxed object.

  >>> def f(x):
  ...    x[0] = 1
  ... 
  >>> a = [ 0, 2, 3 ]
  >>> b = a
  >>> f(a)
  >>> a is b
  True
The f function can do nothing to make "a is b" false.

Under pass-by-reference, assigning to x would do that.

Under pass-by-reference, we can pass a reference to a, such that a can be replaced, without affecting b.

"pass-by {whatever}" refers to the semantics of the parameter and what it receives from the argument expression, and how, not the semantics of the argument object/value.

I don't expect 2 to be printed. I think you're conflating scope with calling conventions. x is locally scoped to the function. `f` behaves like a lambda/closure.

Would you expect `(define f (lambda(x) x))` to have awareness about some global `x`? The only difference with Python is that the closure has access to the global scope unless the variable is locally defined and a new reference is created in the frame.

You're trying to demonstrate dynamic scoping.

Python has lexical scoping and neither set of scoping rules is directly related to passing by reference or value.

Nope, that code actually does demonstrate the difference between pass by reference and pass by value. If you prefer, change either the `x`'s in the function to be `y` or the global `x`'s to by `y` and you'll see the same behavior. Which shows that Python is not, in fact, pass by reference.
I think I see what you are saying (the shared variable threw me), but it STILL isn't doing what you think.

    def s(x):
        x[1] = 2222
        return x

    x = [1,2,3]
    print(s(x)) # prints [1, 2222, 3)
As you can see, it is pass by reference.

Tuples, strings, numbers, and some other things are immutable.

When you pass them, you pass a reference (from the user's perspective as this can be optimized to pass by value as the interpreter sees fit). When you update that reference, because they are immutable, you must update the current reference to a new location in memory. Because you are changing what the new pointer variable in the function points to, of course the other pointer doesn't update.

> As you can see, it is pass by reference.

No, it's passing a reference, but it is still not pass by reference, this is actually worth distinguishing because they are different behaviors. That function (by moonchild) does exactly what moonchild meant it to do, which was to demonstrate that Python is not pass by reference. Another function demonstrating that Python is not pass by reference:

  def swap(a, b):
    a, b = b, a

  a = [1,2]
  b = [3,4]
  print(a,b)
  swap(a,b)
  print(a,b)
If Python were pass by reference (versus passing a reference in these cases) then it would perform a swap, but it does not perform a swap because it is not pass by reference. C++ has pass by reference (as an option), as do Pascal and Ada, and more languages. But Python is not among them. An actual pass-by-reference swap, in C++:

  template<typename T> // template to be the most equivalent to the intended Python code
  void swap(T& a, T& b) {
    T t = a;
    a = b;
    b = a;
  }
That will actually perform a swap when called with `swap(x,y)`. Any language that offers (by option or by default) pass by reference can have an equivalent swap function, including a generic one like the above if the language offers some notion of generics or is dynamically typed (like Python).
Because it is expensive to copy a large container? Which is why you want immutable data structures, in which case copying is superfluous. So the problem with Python is not the aliasing in assignment, rather it is the mutability of the objects that could create unpleasant surprises.