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

cl-letf in Hy #1660

Closed
brandonwillard opened this issue Jul 12, 2018 · 11 comments
Closed

cl-letf in Hy #1660

brandonwillard opened this issue Jul 12, 2018 · 11 comments

Comments

@brandonwillard
Copy link
Member

After a macro expansion, I ended up in a situation (this one, to be exact) like the following:

=> (setv some-dict {"some-key" None})
=> (require [hy.contrib.walk [*]])
=> (let [(get some-dict "some-key") 2]
... (print some-dict))
  File "<input>", line 1, column 7

  (let [(get some-dict "some-key") 2]
        ^------------------------^
HyMacroExpansionError: bind targets must be symbols

For this situation to work out, it seems like let would have to behave like Emacs' cl-letf.

A very related functionality — namely, generalized variables in Emacs — does appear to work in Hy:

=> (setv (get some-dict "some-key") 1)
=> (print some-dict)
{'some-key': 1}

That said, is there any place/interest in having an extended let that allows this (when reasonable) or a separate letf?

@gilch
Copy link
Member

gilch commented Jul 12, 2018

The purpose of Hy's let is to create lexical variables. I'm not certain from your example what you want, but I think you're asking for dynamic variables hylang/hyrule#51, which is the default let behavior in Emacs.

Python does this kind of thing with context managers and the with statement, which Hy can also use. If you want to temporarily mutate a dict, then put it back at the end of the block, you want with, not let. You'd have to write the code to both do and undo your assignment, but it will be reusable.

Another option might be to use a collections.ChainMap in combination with let, to preserve the underlying dict.

=> (let [some-dict (ChainMap {} some-dict)]
...  (setv (get some-dict "some-key") 2)
...  (print some-dict)
...  (print (get some-dict "some-key")))

ChainMap({'some-key': 2}, {'some-key': None})
2
=> (print some-dict)
{'some-key': None}

@brandonwillard
Copy link
Member Author

brandonwillard commented Jul 12, 2018

I didn't think about it in terms of broader variable scoping and/or binding, but I suppose that could be part of it.

However, does that still apply in a situation like the following?

> (let [some-dict {"some-key" None}
...     (get some-dict "some-key") 1]
... (print some-dict))
  File "<input>", line 2, column 6

       (get some-dict "some-key") 1]
       ^------------------------^
HyMacroExpansionError: bind targets must be symbols

Another way to state the functionality I'm describing: alter let to allow any valid assignment target as a target in its bindings.

@gilch
Copy link
Member

gilch commented Jul 12, 2018

If you want to permanently update a target, use setv. And recall that macros can return a do block (named from Clojure's do, which is like the Common Lisp PROGN or Scheme begin).

If you want to update a target for the duration of a block (and then put it back), use with and a patching context manager.

=> (import [unittest.mock [patch]])
None
=> (setv a-dict {"key" None})
None
=> (with [(patch.dict a-dict {"key" 2})]
...  (print a-dict))

{'key': 2}
=> (print a-dict)
{'key': None}

See also: patch.object, patch.multiple, and contextvars. This is dynamic scope.

If you want some kind of lexically-scoped update of a generalized target, then I don't understand what that means.

If you want some kind of destructuring, let doesn't support that, but see #1328. I think a let=: would be possible.

@alphapapa
Copy link
Contributor

Maybe what we need is a hy.cl library. It'd be nice to be able to use letf, flet, labels, etc.

@spiderbit
Copy link

spiderbit commented Jul 17, 2018

I wonder if that problem is no reason to fork? It would be hard to change the muscle-memory from (let (...) foo) to (with [x 5] foo) or something like that. But I think not even that would work.

What is the goal of hy, it should be a as much as possible lisp with a ptyhon backend?

I thought it's just not possible that python has no block scope so you can't implement a lisp like (let) construct, but I just learned aboutd the del() function.

So you could compile:

(let (foo 5) (bar))

into:

foo = 5
bar()
del (foo)

I just wonder, because without let I can't take a lisp like language serious. I don't want to programm python code with a bit different parens syntax.

@gilch
Copy link
Member

gilch commented Jul 17, 2018

A library is not a fork.

Hy should be as much like Python as possible, but homoiconic with macros and expression-oriented like Lisp, with features mostly taken from Clojure, but also Common Lisp.

Python does not have arbitrary block scope, but it does have late-binding lexically-scoped function closures.

del is a statement/special form, not a function. You could certainly write your own macro to do that with del, but that's not how let works, even in Emacs. Think about what happens if you nest them.

let doesn't mean what you think it means. What you're trying to do is dynamic scope. But Hy's let is lexically scoped, like it is in Clojure and Common Lisp, neither of which have a letf. Python doesn't have a separate namespace for function identifiers like Common Lisp and Emacs do. So Hy is more of a Lisp-1, like Scheme and Clojure.

@refi64
Copy link
Contributor

refi64 commented Jul 17, 2018

@spiderbit As already stated above, Hy has a let implementation that mostly does what you want. Using del has been discussed before but rejected because it interferes with closures.

@spiderbit
Copy link

that's good to hear, why do I not see it then in the documentation homepage?

neither there:
http://docs.hylang.org/en/stable/language/api.html#built-ins

nor there:
http://docs.hylang.org/en/stable/tutorial.html#hy-is-a-lisp-flavored-python

where is it hidden? :D

@gilch
Copy link
Member

gilch commented Jul 17, 2018

@brandonwillard
Copy link
Member Author

brandonwillard commented Jul 17, 2018

@gilch, on a somewhat related note, I came across this thread (and this one, too) a while back when searching for letf in CL.

@Kodiologist
Copy link
Member

Kodiologist commented Jul 24, 2018

Anyway, I think @gilch's diagnosis of why the idea of letf doesn't really make sense (at least in Python and with a lexical let) is spot on, so I'm closing this issue.

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

No branches or pull requests

6 participants