elves / elvish

Powerful scripting language & versatile interactive shell
https://elv.sh/
BSD 2-Clause "Simplified" License
5.67k stars 300 forks source link

Declare/override functions with eval #1256

Open uranusjr opened 3 years ago

uranusjr commented 3 years ago
$ fn x []{ echo 'x outside' }
$ y = 'y outside'
$ x
x outside
$ echo $y
y outside

$ echo (slurp < x.elv)
fn x []{ echo 'x inside' }
y = 'y inside'

$ eval (slurp < x.elv)

$ x
x outside
$ echo $y
y inside

eval can set value variables, but not functions. This makes it difficult to implement something like Bash’s activate.

zzamboni commented 3 years ago

According to the eval documentation, eval does not act on the current namespace, but (by default) on a copy of it. If you want to copy things back to the interactive namespace, you can use edit:add-var. You can see an example of this in my alias module.

uranusjr commented 3 years ago

In that case, what is the mechanism that variable modifications are propagated back to the parent namespace (the one that runs eval)? And would it be possible for functions and variables to behave the same?

I think the main issue I’m having is less about the functions not being copied back, but more that the behaviour is confusing.

zzamboni commented 3 years ago

I don't really understand why this is happening, but maybe @xiaq can clarify.

What I did notice is that if you assign a value to the x~ variable, then the change gets reflected in the current namespace:

> /bin/cat x.elv
x~ = []{ echo 'x inside' }
y = 'y inside'
> eval (slurp < x.elv)
> x
x inside
xiaq commented 3 years ago

Variable names are statically checked. This is why eval is not allowed to create or delete names.

Variable values are dynamic. This is why eval can reassign existing variables.

The namespace eval gets is a copy of the local scope it is invoked from, but it's a shallow copy - the variables in the namespaces themselves are aliased.

krader1961 commented 3 years ago

@uranusjr wrote

eval can set value variables, but not functions.

What your example does is set variables visible (i.e., aliased) in the eval scope and define functions in the eval lexical scope. The fn keyword does not just "set" a function variable; i.e., assign a lambda to a function variable. It also defines (i.e., creates) the function variable. Which, because of the eval lexical scope, is not visible in the callers scope. That's why @zzamboni's approach "works". TBD is how to modify the documentation to make this subtle point easier to discover.

P.S., @uranusjr your original problem statement is a great example of how such issues should be written. It was concise and unambiguous. I look forward to many more questions and bug reports from you and suspect the same is true for much of the Elvish community.

uranusjr commented 3 years ago

What your example does is set variables visible (i.e., aliased) in the eval scope and define functions in the eval lexical scope. The fn keyword does not just "set" a function variable; i.e., assign a lambda to a function variable. It also defines (i.e., creates) the function variable. Which, because of the eval lexical scope, is not visible in the callers scope. That's why @zzamboni's approach "works". TBD is how to modify the documentation to make this subtle point easier to discover.

Thanks for the clear explaination! This makes more sense to me now. Some documentation on this would be awesome 🙂 (Also to make clear whether @zzamboni’s approach would be guaranteed to work while Elvish’s implementation progresses.)