elves / elvish

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

The blackhole variable _ as a function argument #1141

Open hanche opened 4 years ago

hanche commented 4 years ago

The blackhole variable _ discards any value assigned to it, and usage always returns $nil. Currently, the same can not be said of _ as a function argument:

⬥ fn foo [_]{ put $_ }
⬥ foo bar
⮕ bar

This inconsistency ought to be fixed.

(Sorry, I accidentally submitted this before I had finished typing it. Not sure how it happened.)

krader1961 commented 4 years ago

Consistency would seem to require that _ as a function parameter be treated as a blackhole assignment when the function is used. On the other hand that is subtle enough it's likely to cause bugs. So I'm inclined to favor making it a compile time error to use _ as a function parameter. If you want to indicate that a parameter is meant to be ignored I favor doing it the way it's done in many Python programs: fn foo [arg]{ _ = $arg; put $_ }.

zzamboni commented 4 years ago

@krader1961 it seems to me that consistency would dictate that _ is allowed as a function argument, but it "swallows" the value just like assigning to $_ does.

This is also consistent with the behavior in some LISPs (e.g. Emacs-LISP, not sure about others) which consider variables starting with an underscore to indicate an unused variable (this convention is also implemented in the byte-compiler, which does not produce "unused variable" warnings for them).

Allowing the declaration of unused function arguments might seem odd at first, but it's useful in the face of hooks or other integration entry points, which in some cases might deliberately want to ignore some of the arguments passed. Off the top of my head, an after-readline hook which is used to measure execution time may want to ignore the command passed to it, e.g. something like fn start-command [_]{ start-time = (date) }.

krader1961 commented 4 years ago

Note: The blackhole var is valid as an option:

> [&_=v]{ echo $_ } &_=v2
v2

That should probably be disallowed.

Regarding @zzamboni's previous comment I'm not convinced underscore should be legal as a parameter name. If you give the parameter any other name it is still silently ignored if not used. An Elvish linter should warn about unused parameters. But I don't think that justifies allowing underscore as a parameter name. Perhaps my years of writing Python at Google is influencing me too much but I don't think it's an undue burden to add a throwaway assignment in the body of the function; e.g., _ = $unused_param.

hanche commented 4 years ago

I think I am beginning to understand the nature of the blackhole variable a bit better, after a few experiments that, at first, confused me greatly, but now makes perfectly good sense: The blackhole variable $_ is just like any other variable in the builtin namespace, except for its magic quality of always having the value $nil, even after you assigned some other value to it. But a variable named _ in any other namespace is just an ordinary variable, satisfying the same rules as any other variable. That explains its apparently anomalous behaviour as function arguments: It becomes a variable in the the local namespace. You can get the same effect by assigning to local:_ inside the function body.

⬥ [_]{ _ = _$_; put $_ } x
⮕ _x
⬥ { local:_ = x; put _$_ }
⮕ _x

In retrospect, the documentation certainly does not contradict this, but it could have been more explicit. (A language lawyer might say it is already clear enough, but it sure had me fooled.)

You can even make a _ variable in the interactive namespace with the help of -exports- in rc.elv. (That would be a really bad idea, though.)

krader1961 commented 4 years ago

... experiments that, at first, confused me greatly, but now makes perfectly good sense

That would seem to argue for a change in the current behavior of the blackhole, "_", var. The blackhole var, in my opinion, should not be treated as a typical var. That is, without regard to whether the context resolves to the builtin, or some other, namespace. It should always be treated specially by the compiler without regard to the namespace. Also, that's without regard to whether it should be a compiler error to use it as a function parameter or whether that use as a function parameter should behave the same as the builtin blackhole var.

hanche commented 2 years ago

I just noticed another not-special case of the “_” variable, not discussed above: The new(ish) var declaration lets you create one. This should not come as a surprise, since var will create new variables, never overwrite an existing variable of the same name.

⬥ set _ = hi
⬥ put $_
⮕ $nil
⬥ var _ = ho
⬥ put $_
⮕ ho
⬥ del _
⬥ put $_
⮕ $nil

I was in fact caught by surprise, thought I shouldn't have been surprised, when I ran

some command | var _ _ a b

in order to capture the third and fourth output of some command and discovered that additionally, $_ had captured the second output. A quick del _ later, life returned to normal. (Not that I ever rely on $_ to be nil.)

xiaq commented 2 years ago

@hanche The fact that var allows you to create a new $_ is also consistent with how other builtin variables behave - you can shadow them. Your observation that $_ is just another builtin variable correct, this is indeed how it is implemented.

Making $_ a blackhole everywhere will require a language change, but it seems like a good idea. You won't need it for functions that are supposed to be called directly, but it's useful when defining callbacks to explicitly discard some parameters that won't be used. It is also useful as an assignment LHS to discard some unneeded values.

It's also worthwhile to make using $_ just a compilation error, too.