Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eval should use globals + locals #1043

Closed
wants to merge 1 commit into from
Closed

Conversation

refi64
Copy link
Contributor

@refi64 refi64 commented Apr 12, 2016

Fixes #1041. @gilch Can you confirm this fixes it for you?

@gilch gilch mentioned this pull request Apr 12, 2016
@gilch
Copy link
Member

gilch commented Apr 12, 2016

Travis is complaining. Also globals+locals isn't the current environment, since nonlocals don't work e.g.

(((fn []
    (setv spam 42)
    (fn [] (eval 'spam)))))
;; NameError

But something similar in Clojure would work:

user=> (((fn [] (let [spam 42] (fn [] (eval 'spam))))))
42

It also works in Hy without the eval:

=> (((fn []
... (setv spam 42)
... (fn [] spam))))
def _hy_anon_fn_2():
    spam = 42

    def _hy_anon_fn_1():
        return spam
    return _hy_anon_fn_1
_hy_anon_fn_2()()
42

@gilch
Copy link
Member

gilch commented Apr 12, 2016

Another potential issue: eval should be able to mutate a global or local with setv. It currently can't mutate locals, but it can for globals. If you make a new dict with globals+locals, then eval can't update globals by writing to it either.

Overall, Hy's eval seems to work more like Python's than like Lisp's. I wonder if this is the wrong approach. How does our REPL even function without a proper eval? Can we just use that for eval instead?

@agentultra
Copy link
Member

A typical Lisp eval, as in Common Lisp, accepts a form as the parameter and computes its value using the dynamic environment to provide meaning for names in the form. The user eval is really a front-end for the internal one which takes an environment as a parameter as macro-expand-1 does.

Maybe there’s a way to map the Python concept of module-level scope to a dynamic environment in Lisp?

On Apr 12, 2016, at 4:30 PM, Matthew Egan Odendahl [email protected] wrote:

Another potential issue: eval should be able to can mutate a global or local with setv. It currently can't mutate locals, but it can for globals. If you make a new dict with globals+locals, then eval can't update globals by writing to it either.

Overall, Hy's eval seems to work more like Python's than like Lisp's. I wonder if this is the wrong approach. How does our REPL even function without a proper eval? Can we just use that for eval instead?


You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub

@refi64
Copy link
Contributor Author

refi64 commented Apr 14, 2016

@gilch Well that sucks...

There's only one true solution I can think of: do it like shown in this PR, but also merge it with the result of a new function, hy.?.get_freevars, which would go something like this:

def get_freevars(func):
    closure = func.__closure__
    freevars = func.__code__.co_freevars
    return dict(zip(freevars, (cell.cell_contents for cell in closure)))

Now, fn could be amended do always do an assignment like this as the first part of the function:

def _hy_anon_fn_1(): # Or whatever...
    _hy_current_function = _hy_anon_fn_1

then eval could pass _hy_current_function to get_freevars. The assignment would only be necessary in the case of eval being used; otherwise, the compiler wouldn't need to put it.

@gilch
Copy link
Member

gilch commented Apr 14, 2016

There's still the problem of assignment. What happens if you (eval '(setv foo 42))? I'm also not sure if that __code__ trick will work in non-CPython implementations (pypy? jython?), or at least not the same way.

I'm not sure how well the _hy_current_function will work. I can see that if you can access a closure like that, can you could implement nonlocal in Python 2, but what happens in this case?

def _hy_anon_fn_1():
    _hy_current_function = _hy_anon_fn_1
    foo = 42
    def _hy_anon_fn_2():
        _hy_current_function = _hy_anon_fn_1  # used in _hy_anon_fn_3, so compiler put it in.
        new_hy_eval(HySymbol('foo'),_hy_current_function)  # oops
        def _hy_anon_fn_3(): ...

@refi64
Copy link
Contributor Author

refi64 commented Apr 14, 2016

@gilch At this point, I would just deem assignment broken, especially since it's virtually impossible under Python 3 (in Python 2, you could just put eval '' at the top).

Back to the topic...

...

...actually, can't we just define eval in user space in hy.core.macros? It would have code duplication with hy.importer.hy_eval, but it would also be pretty much 100% guaranteed to work, since it's just a macro.

@gilch
Copy link
Member

gilch commented Apr 14, 2016

That does sound like a better plan, if it works. However, that would mean you can't pass eval to higher-order functions, like map. Is there a workaround for that? Maybe we need two evals.

@refi64
Copy link
Contributor Author

refi64 commented Apr 14, 2016

@gilch You can't even do that right now; Hy will pass Python's eval instead. Maybe Hy's eval should be named hy-eval to make a distinction?

@refi64
Copy link
Contributor Author

refi64 commented Jul 7, 2016

Can't figure this out, and sadly don't have the time. :(

@refi64 refi64 closed this Jul 7, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

eval can only see locals
3 participants