hyrule icon indicating copy to clipboard operation
hyrule copied to clipboard

`->` no longer works with dot expression

Open asimjalis opened this issue 1 year ago • 7 comments

REPL session

=> (setv x 10)
x = 10
None
------------------------------
=> x.real
x.real
------------------------------
10
=> (. x real)
x.real
------------------------------
10
=> (-> x (. real))
real(x)
------------------------------
Traceback (most recent call last):
  File "stdin-32a4586a6638a8de7eec78e48c4e19dfcd34b12b", line 1, in <module>
    (-> x (. real))
              ^^^
NameError: name 'real' is not defined

Expected: (-> x (. real)) should produce 10 just like (. x real). It should not produce the error NameError: name 'real' is not defined.

hyrule version: 0.5.0 Hy version: 0.28

asimjalis avatar Feb 23 '24 23:02 asimjalis

Note this used to work in older versions of Hy. It is possible that the recent changes to -> had the effect of breaking this.

asimjalis avatar Feb 23 '24 23:02 asimjalis

This looks like the correct behavior to me. (. real) compiles to just real, so (-> x (. real)) should be equivalent to (-> x real), which produces the same error.

Kodiologist avatar Feb 23 '24 23:02 Kodiologist

@scauligi You were working on argmove macros relatively recently; what do you think?

Kodiologist avatar Feb 23 '24 23:02 Kodiologist

Hmm, I think they have a point here:

(defmacro dot [#* syms] `(. ~@syms))
(-> x (dot real))

compiles to x.real, even though (-> x (. real)) compiles to real(x).

The problem is that the reader parses forms like str.upper directly into Expression([Symbol('.'), Symbol('str'), Symbol('upper')), so macros can't tell the difference between str.upper and (. str upper).

However, I think it's reasonable to expect that while (-> x foo.bar) compiles to (foo.bar x), the form (-> x (. foo bar)) should compile to (. x foo bar).

This would require a change to Hy's AST, to allow for a distinction somehow between 'foo.bar and '(. foo bar)


In previous versions of Hy, foo.bar parsed to Symbol('foo.bar') instead of Expression(...); however, this lead to problems where macros weren't treating (. foo bar) like foo.bar (iirc) which is why we changed it.

I'm not quite sure what the right solution here is... Potentially we could add a flag or other metadata to the Expression form when we parse foo.bar so that macros (like ->) that care can make a distinction?

scauligi avatar Feb 24 '24 01:02 scauligi

Ah geez, I don't want to change Hy's built-in syntax to make -> slightly more convenient. If you're sure you want (-> x (. real)) to compile to x.real, then bite the bullet and make (-> x foo.bar) compile to (. x foo bar), too.

Kodiologist avatar Feb 24 '24 03:02 Kodiologist

I guess there's always the ultimate flexibility obtainable by making -> a reader macro instead.

Kodiologist avatar Feb 24 '24 03:02 Kodiologist

Here is an example of where the old simpler interpretation of -> is useful.

; Find parent directory path of code where current stack frame is running.
(-> (inspect.currentframe) (inspect.getframeinfo) (. filename) (Path) (.resolve) (. parent) (str) )

The operator -> has very simple semantics and is easy to reason about. It is fun to grow code organically as above exploring the methods and attributes of each successive result.

Here is the above line in a self-contained executable snippet. I’m overriding the latest -> with an older version so the snippet runs without errors.

(defmacro -> [head #* args]
  (setv ret head)
  (for [node args]
    (setv ret (if (isinstance node hy.models.Expression)
                  `(~(get node 0) ~ret ~@(cut node 1 None))
                  `(~node ~ret))))
  ret)

(import inspect)
(import pathlib [Path])

(-> (inspect.currentframe) (inspect.getframeinfo) (. filename) (print)) 
(-> (inspect.currentframe) (inspect.getframeinfo) (. filename) (Path) (.resolve) (. parent) (str) (print))

asimjalis avatar Feb 24 '24 21:02 asimjalis

Hi, I had found the same problem referenced in this issue using the pandas library for which the threading macro is really convenient.

(import pandas :as pd)
(setv df (pd.DataFrame {"a" [1, 1, 2, 2, 3] "b" [4, 5, 6, 7, 8]}))
(print (get (. df shape) 0))
(print (-> 
           (. df shape)
           (get 0)
           ))
(print (-> df
           (. shape)
           (get 0)
           ))

The first two print calls work as expected printing 5 as the number of rows. But the last one fails:

Traceback (most recent call last):
  File "...", line 8, in <module>
    sys.exit(hy_main())
             ^^^^^^^^^
  File "<frozen runpy>", line 291, in run_path
  File "<frozen runpy>", line 98, in _run_module_code
  File "<frozen runpy>", line 88, in _run_code
  File "...", line 81, in <module>
    (. shape)
        ^^^^
NameError: name 'shape' is not defined

I struggle to understand why the second works but not the third, not sure if this is a different situation from the previous ones in this thread? Hy version: 1.0.0 Hyrule version: 0.7.0

msicilia avatar Sep 25 '24 17:09 msicilia

Yeah, looks the identical problem to me. (-> df (. shape) (get 0)) is better written (. df shape [0]) or (get df.shape 0), anyway.

Kodiologist avatar Sep 25 '24 18:09 Kodiologist