Repository: hylang/hy

Issue:
Confusing `setv` Semantics
`setv` is inconsistent, presumably because python itself has some odd corners in its semantics.

In the following we have:

```
=> (import [numpy :as np])
=> (defn mycos [x] (np.cos x))
=> (mycos 2)
-0.41614683654714241
=> (setv np.cos (fn [x] 1))
=> (mycos 2)
-0.41614683654714241
```

We'd really expect `(mycos 2)` to return `1` here...  And with a little misdirection, we can achieve this:

```
=> (import [numpy :as np])
=> (defn mycos [x] (np.cos x))
=> (mycos 2)
-0.41614683654714241
=> (setv foocos (fn [x] 1))
=> (setv np.cos foocos)
<function foocos at 0x108ce46e0>
=> (mycos 2)
1L
```

Shouldn't these two code blocks return the same thing?

Confusing `setv` Semantics
`setv` is inconsistent, presumably because python itself has some odd corners in its semantics.

In the following we have:

```
=> (import [numpy :as np])
=> (defn mycos [x] (np.cos x))
=> (mycos 2)
-0.41614683654714241
=> (setv np.cos (fn [x] 1))
=> (mycos 2)
-0.41614683654714241
```

We'd really expect `(mycos 2)` to return `1` here...  And with a little misdirection, we can achieve this:

```
=> (import [numpy :as np])
=> (defn mycos [x] (np.cos x))
=> (mycos 2)
-0.41614683654714241
=> (setv foocos (fn [x] 1))
=> (setv np.cos foocos)
<function foocos at 0x108ce46e0>
=> (mycos 2)
1L
```

Shouldn't these two code blocks return the same thing?



Discussion:
Again, because Hy compiles to Python, it inherits Python's semantics. Perhaps email python-dev with how they can change assignment? 

Thanks for your patience with me, Paul :D

I'm not sure what to say to them.  I tried the following:

```
=> (disassemble '(do (import [numpy :as np]) (defn mycos [x] (np.cos x)) (setv np.cos (fn [x] 1)) (mycos 2)) true)
import numpy as np

def mycos(x):
    return np.cos(x)

def np.cos(x):
    return 1L
mycos(2L)
```

And the code generated doesn't run in the ordinary python interpreter ; it appears that Hy is generating a function with a "." in its name in this case?

The other code-block compiles to something which can run in the python interpreter:

```
=> (disassemble '(do (import [numpy :as np]) (defn mycos [x] (np.cos x)) (setv foocos (fn [x] 1)) (setv np.cos foocos) (mycos 2)) true)
import numpy as np

def mycos(x):
    return np.cos(x)

def foocos(x):
    return 1L
np.cos = foocos
np.cos
mycos(2L)
```

Maybe an epicycle is in order when `setv` is called to assign a function to an attribute?

> Maybe an epicycle is in order when setv is called to assign a function to an attribute?

ie, I am volunteering if you think this is okay

Again, because Hy compiles to Python, it inherits Python's semantics. Perhaps email python-dev with how they can change assignment? 

Thanks for your patience with me, Paul :D

I'm not sure what to say to them.  I tried the following:

```
=> (disassemble '(do (import [numpy :as np]) (defn mycos [x] (np.cos x)) (setv np.cos (fn [x] 1)) (mycos 2)) true)
import numpy as np

def mycos(x):
    return np.cos(x)

def np.cos(x):
    return 1L
mycos(2L)
```

And the code generated doesn't run in the ordinary python interpreter ; it appears that Hy is generating a function with a "." in its name in this case?

The other code-block compiles to something which can run in the python interpreter:

```
=> (disassemble '(do (import [numpy :as np]) (defn mycos [x] (np.cos x)) (setv foocos (fn [x] 1)) (setv np.cos foocos) (mycos 2)) true)
import numpy as np

def mycos(x):
    return np.cos(x)

def foocos(x):
    return 1L
np.cos = foocos
np.cos
mycos(2L)
```

Maybe an epicycle is in order when `setv` is called to assign a function to an attribute?

> Maybe an epicycle is in order when setv is called to assign a function to an attribute?

ie, I am volunteering if you think this is okay


Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -1751,18 +1751,19 @@ def compile_def_expression(self, expression):
     def _compile_assign(self, name, result,
                         start_line, start_column):
         result = self.compile(result)
-
-        if result.temp_variables and isinstance(name, HyString):
-            result.rename(name)
-            return result
-
         ld_name = self.compile(name)
-        st_name = self._storeize(ld_name)
 
-        result += ast.Assign(
-            lineno=start_line,
-            col_offset=start_column,
-            targets=[st_name], value=result.force_expr)
+        if result.temp_variables \
+           and isinstance(name, HyString) \
+           and '.' not in name:
+            result.rename(name)
+        else:
+            st_name = self._storeize(ld_name)
+            result += ast.Assign(
+                lineno=start_line,
+                col_offset=start_column,
+                targets=[st_name],
+                value=result.force_expr)
 
         result += ld_name
         return result


Test patch:
diff --git a/tests/native_tests/core.hy b/tests/native_tests/core.hy
--- a/tests/native_tests/core.hy
+++ b/tests/native_tests/core.hy
@@ -57,6 +57,34 @@
   (try (do (dec None) (assert False))
        (catch [e [TypeError]] (assert (in "not a number" (str e))))))
 
+(defn test-setv []
+  "NATIVE: testing setv mutation"
+  (setv x 1)
+  (setv y 1)
+  (assert-equal x y)
+  (setv x (setv y  12))
+  (assert-equal x 12)
+  (assert-equal y 12)
+  (setv x (setv y (fn [x] 9)))
+  (assert-equal (x y) 9)
+  (assert-equal (y x) 9)
+  (try (do (setv a.b 1) (assert False))
+       (catch [e [NameError]] (assert (in "name 'a' is not defined" (str e)))))
+  (try (do (setv b.a (fn [x] x)) (assert False))
+       (catch [e [NameError]] (assert (in "name 'b' is not defined" (str e)))))
+  (import itertools)
+  (setv foopermutations (fn [x] (itertools.permutations x)))
+  (setv p (set [(, 1 3 2) (, 3 2 1) (, 2 1 3) (, 3 1 2) (, 1 2 3) (, 2 3 1)]))
+  (assert-equal (set (itertools.permutations [1 2 3])) p)
+  (assert-equal (set (foopermutations [3 1 2])) p)
+  (setv permutations- itertools.permutations)
+  (setv itertools.permutations (fn [x] 9))
+  (assert-equal (itertools.permutations p) 9)
+  (assert-equal (foopermutations foopermutations) 9)
+  (setv itertools.permutations permutations-)
+  (assert-equal (set (itertools.permutations [2 1 3])) p)
+  (assert-equal (set (foopermutations [2 3 1])) p))
+
 (defn test-distinct []
   "NATIVE: testing the distinct function"
   (setv res (list (distinct [ 1 2 3 4 3 5 2 ])))