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

More explicit syntax for temporary assignments #1114

Closed
xiaq opened this issue Aug 17, 2020 · 16 comments
Closed

More explicit syntax for temporary assignments #1114

xiaq opened this issue Aug 17, 2020 · 16 comments
Milestone

Comments

@xiaq
Copy link
Member

xiaq commented Aug 17, 2020

The current syntax for temporary assignments is borrowed from POSIX shell, and requires there to be no spaces surrounding a =, e.g.

foo=bar put $foo
pwd=/tmp touch x

The syntax is quite subtle, and is a source of syntactical complexity - for example, the syntax for lvalues have to support braced lists, in order for code like this to work (suppose that f outputs two values):

{a,b}=(f) echo $a $b

It may be a good idea to make the syntax for temporary assignments more explicit, for example using a special command with. The examples above can be written as:

with foo = bar { put $foo }
with pwd = /tmp { touch x }
with a b = (f) { echo $a $b }

However, it's not straightforward to generalize this to multiple temporary assignments, which can be done easily using the current syntax, such as a=foo b=bar put $a $b. Nesting with certainly works, but is quite verbose. One possibility is to use lists to surround the individual assignments:

with [a = foo] [b = bar] { put $a $b }

In any case, the with command is going to be more verbose than the current syntax, hence the "maybe" tag.

@krader1961
Copy link
Contributor

FWIW, the POSIX semantics for temporary assignments work by instantiating environment variables. Except when it doesn't. That is, x=y ... is (mostly) equivalent to env x=y .... It's an example of a rather silly optimization to eliminate four characters from a statement. The "mostly" is parenthesized because the behavior depends on whether the command is a builtin or external. Even that distinction has subtleties. Consider the following:

bash-5.0$ WTF=wtf eval 'echo $WTF'
wtf
bash-5.0$ WTF=wtf echo $WTF

bash-5.0$ WTF=wtf typeset | grep WTF
bash-5.0$ WTF=wtf typeset -x | grep WTF
bash-5.0$ WTF=wtf eval 'typeset -x' | grep WTF
declare -x WTF="wtf"
bash-5.0$ WTF=wtf env | grep WTF
WTF=wtf
bash-5.0$ type env
env is hashed (/usr/bin/env)

Which is a long winded way of saying that any change to the syntax or behavior of temporary assignments requires careful thought so as not to repeat the mistakes of the POSIX standard.

@krader1961
Copy link
Contributor

krader1961 commented Sep 17, 2020

See also, issue #705. Consider the unexpected behavior of this example:

> x = 1
> put $x
▶ 1
> x=2
> put $x
▶ 2
> x=3 true
▶ 2

The second and third "put" should have output 1 rather than 2. If this issue were implemented that would probably resolve issue #705.

@xiaq xiaq removed the A:Language label Oct 28, 2020
@krader1961
Copy link
Contributor

This comment is indirectly related to this issue since the problem I'm describing involves permanent, rather than temporary, assignments. Consider this pipeline:

echo a=b | cut -d = -f 1

In a POSIX shell (e.g., bash) that will output a. In Elvish it outputs nothing because the RHS of the pipeline performs two assignments: cut = -f and -d = 1. That pipeline is a simplified version of a more complex pipeline. I am not arguing that the current Elvish semantics are wrong and should be changed. I'm simply pointing out that the surprising behavior of the above pipeline should be considered in resolving this issue.

@xiaq
Copy link
Member Author

xiaq commented Dec 7, 2021

Getting back to this. I'm considering stealing a design from Raku.

Introduce a special command (say tmp) that does temporary assignment within the current scope. An example:

var x = foo
{
  tmp x = bar
  # $x is "bar" for the remainder of the scope
  command using $x
}
# $x reverts to "foo"

The original original with proposal had the downside that it was tricky to do multiple temporary assignments. With tmp, just have multiple tmp commands:

var x = foo
var y = lorem
{
  tmp x = (some command)
  tmp y = (some other command)
  command using $x and $y
}

One-liner length comparison:

x=foo some command
{ tmp x = foo; some-command }
with x = foo { some-command }

Still more verbose than the current syntax, but same as the original with proposal.

@xiaq
Copy link
Member Author

xiaq commented Dec 7, 2021

Also, if there is already a scope, using tmp is actually more concise.

For example, to and run something using each subdirectory as the working directory:

for d [*/] {
  tmp pwd = $d
  # do things in $d
}

While the current syntax will require an additional level of nesting, unless there's only one command in the body:

for d [*/] {
  pwd=$d {
    # do things in $d
  }
}

Another nice thing about tmp is that it has exactly the same syntax with var and set (and the same length; this is why I shortened Raku's temp to tmp).

@xiaq xiaq removed the maybe label Dec 7, 2021
@xiaq xiaq added this to the 0.18.0 milestone Dec 7, 2021
@fennewald
Copy link

I like the idea, if only I could suggest let in lieu of tmp. It's javascript's syntax for the same construct, and I prefer it for purely ergonomic reasons. Another option is the even shorter my from perl, again with the same behavior.

@xiaq
Copy link
Member Author

xiaq commented Dec 8, 2021

This new construct has no corresponding construct in JavaScript or Perl. It does correspond to Bash's local declaration.

Both JavaScript's let and Perl's my are lexical declarations and correspond to Elvish's var.

@krader1961
Copy link
Contributor

I don't think referencing Bash's local keyword clarifies the matter and should be avoided. See, for example, https://stackoverflow.com/questions/4419704/differences-between-declare-typeset-and-local-variable-in-bash. The distinction between typeset, declare and local is a muddle in Bash and other POSIX shells. Having said that, a focus on how the proposed tmp keyword differs from var for magic vars like pwd would help. I'm basically a +1 on the proposal to introduce a tmp keyword from a functionality perspective. The challenge will be documenting the behavior (I found the Raku document linked to above borderline incomprehensible).

@fennewald
Copy link

I've done a little more research, and did originally misunderstand. Would tmp be dynamically scoped, like local in perl?

var x = 1

fn foo []{
  echo $x
}

fn bar []{
  tmp x = 2
  foo
}

foo
bar
foo

Would this print 1, 2, 1?

@hanche
Copy link
Contributor

hanche commented Dec 8, 2021

Would this print 1, 2, 1?

That's how I read it. But I wasn't sure, until I saw the pwd example. And otherwise, it would be just like var. In fact, many of the examples given would produce the same result using var, except for what's under the hood.

@xiaq
Copy link
Member Author

xiaq commented Dec 8, 2021

@fennewald You're right.

Indeed Perl's local does this too - it is confusingly named and Raku's name temp better reflects what it does.

@iandol
Copy link
Contributor

iandol commented Dec 8, 2021

A +1 for tmp

xiaq added a commit that referenced this issue Dec 9, 2021
xiaq added a commit that referenced this issue Jan 3, 2022
@xiaq
Copy link
Member Author

xiaq commented Jan 3, 2022

0.18.0 will deprecate the legacy syntax.

After 0.18.0 is released, the legacy syntax will be removed. Moving this to the 0.19.0 milestone.

@xiaq xiaq modified the milestones: 0.18.0, 0.19.0 Jan 3, 2022
@ilius
Copy link

ilius commented Jan 8, 2022

I think variables should be limited to their scope, unless specified otherwise, like Python's global or nonlocal.

If we support a simple scope with { ... } which is like ( ... ) except it doesn't return the value, just like Go, then instead of

with [a = foo] [b = bar] { put $a $b }

we write

{ var a = foo; var b = bar; put $a $b }

Also maybe a new command to set multiple variables like

{ vars &a = foo &b = bar; put $a $b }

Doesn't make much different in verbosity though.

@xiaq
Copy link
Member Author

xiaq commented Jan 14, 2024

@ilius Variables in Elvish are already lexically scoped, so in { var a = foo; var b = bar; put $a $b }, the variables $a and $b are not visible outside the closure. This issue was about giving an already existing variable a temporary value for a certain scope.

Anyway this has been implemented; I'll leave this open until the legacy syntax is fully removed.

@xiaq
Copy link
Member Author

xiaq commented Jul 23, 2024

I've decided to implement the originally proposed with command as well: tmp is technically fine, but it can sometimes feel counter-intuitive (#1776 (comment))

xiaq added a commit that referenced this issue Jul 24, 2024
@xiaq xiaq moved this from 🗃Shelve to 🧑‍💻Code in All Elvish issues Jul 24, 2024
@xiaq xiaq closed this as completed in 2f51ce6 Jul 26, 2024
@github-project-automation github-project-automation bot moved this from 🧑‍💻Code to ✅Done in All Elvish issues Jul 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

No branches or pull requests

6 participants