8000 proposal: spec: same-scope “everychange” variables · Issue #69008 · golang/go · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

proposal: spec: same-scope “everychange” variables #69008

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

Closed
3 of 4 tasks
lucafabbian opened this issue Aug 22, 2024 · 2 comments
Closed
3 of 4 tasks

proposal: spec: same-scope “everychange” variables #69008

lucafabbian opened this issue Aug 22, 2024 · 2 comments
Labels
gabywins LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal
Milestone

Comments

@lucafabbian
Copy link

Go Programming Experience

Experienced

Other Languages Experience

JS/TS, Go, Kotlin/Java, Python, C, C++, Rust

Related Idea

  • Has this idea, or one like it, been proposed before?
  • Does this affect error handling?
  • Is this about generics?
  • Is this change backward compatible? Breaking the Go 1 compatibility guarantee is a large cost and requires a large benefit

Has this idea, or one like it, been proposed before?

This proposal introduces the "everychange" keyword. The keyword puts a trap on assignments of a specific variable within the function scope, in a similar way to the "defer" keyword. The main benefit would be to allow better error handling: one could make a code run everytime an err variable is set.

The closest proposal to this one #67316 suggests adding a trap using the “select” keyword. In my opinion, that is not the right approach.
Using an old keyword “hides” the change and makes the flow difficult to understand. Most notably, it makes errors “special” by assigning them a special behavior and is not backward compatible. In that proposal, errors are treated with a convoluted syntax and there is no easy way to understand when a trap triggers.

Does this affect error handling?

The biggest selling point of this proposal is that it is not a proposal about error handling, but just a proposal which happens to make error handling easier to deal with.

Most other proposals try to make the code more concise by introducing magic and unclear flow. They set default values and behaviors.
This proposal, instead, takes a different approach by being very explicit: no default return, no default behavior, no trying to guess if a function’s last argument is an error. With this proposal everything must be written by the developer.

Is this about generics?

No.

Proposal

Introduction

Many high level languages are implementing ways to watch for variable changes. JavaScript proxies and signals, for example, allow developers to react every time a variable or an object property is set, and compute derived values or execute actions accordingly.
Golang has always strived for being simple, and thus those high level patterns should not be considered for insertion inside the language core, especially since they are so convoluted. Nothing says, however, that Golang needs a full-featured set of keywords and objects to work with reactive variables.
The aim of this proposal is to prove that an extremely simplified way to observe assignment would still be really beneficial, especially since it would provide a clear and concise way to perform error handling. This approach differs from other proposals by not breaking the “errors are values” rule.

Proposal explanation and code example

This proposal introduces only one keyword, “everychange”, which lets you specify a code to run every time a chosen variable is assigned with a value. It's a trap that is invoked every time the variable is assigned inside the same function.
To make obvious that a variable runs something when assigned, those variables should be named in a special way, i.e. by prefixing them with #. Only local variables could be observed (no global variables, no function args). The everychange keyword must be specified in the exact same scope of the observed variable, preferably right after the variable declaration.

For example:

func printMessageOnChange(){
  #msg := ""
  everychange #msg {
    fmt.Println(#msg)
  }


  #msg = "hello!"
  #msg = "hello again!"
}

The following code should print "hello\nhello again!" and should be equivalent to:

func printMessageOnChange(){
  msg := ""


  msg = "hello!"
  fmt.Println(msg)
  msg = "hello again!"
  fmt.Println(msg)
}

The real power of everychange comes when performing error handling:

func fn() (string, Error) {
  var #err Error
  everychange #err {
    if #err != nil {
      return "", #err
    }
  }


  result, #err := dangerousFunction()
  return result, nil
}

Motivation

Many proposals try to solve error handling by introducing special constructs which are valid for errors and nothing else. This would often result in contradicting the “errors are value” mantra, and provide magical control flows which are hard to debug. This proposal provides a way to handle errors without breaking Golang’s core philosophy.

The extreme simplicity of the keywords and the harsh restrictions makes it really difficult to abuse it or to introduce an hidden control flow: developers would learn very quickly that when they see an assignment of a prefixed #variable, they should check the variable declaration. Forcing everything to be in the same scope would help debugging.

Some specs

  • “everychange variables” should be allowed only as local variables. Function args are not eligible, nor are global variables
    The “everychange” keyword must have the exact same scope as the variable. It's forbidden to nest everychange inside an inner if or a for.
  • The trigger runs only when the assignment is performed in the same function and in the same scope where the everychange keyword was specified. Inner scopes trigger the everychange code too, inner functions do not.
  • There may be as many everychange as wanted. If a single assignment involves multiple “everychange variables”, the triggers are applied in the same order they were declared. The assignment order of the variables is irrelevant.
  • Every everychange trigger runs in its own scope.
  • The everychange code runs only on subsequent assignments.
  • Even if the everychange trigger is run later on, the code may not refer to future variables, functions and so on.
  • The everychange trigger code may include a return statement.
  • Variables prefixed with # should never be marked as "unused" (this may require further discussion)

Language Spec Changes

The two main changes are:

  • the introduction of a new keyword, everychange
  • a new naming convention for local variables, which could now start with # (or any equivalent).

Informal Change

Thanks to a new keyword, everychange, you can now run a code everytime a specific variable is assigned. Could be used only on local variables and those variables should have a name starting with #.

For example:

func fn() (string, Error) {
  var #err Error
  everychange #err {
    if #err != nil {
      return "", #err
    }
  }


  result, #err := dangerousFunction()
  return result, nil
}

Is this change backward compatible?

Yes.

Orthogonality: How does this change interact or overlap with existing features?

One of the goal of this proposal is to overlap as less as possible with existing features.

Would this change make Go easier or harder to learn, and why?

Would obviously introduce a new feature to learn; however, the proposal is made in order to be self explanatory and with no magic behind - it should be easier to grasp when compared with most other error handling proposals.

Cost Description

Runtime: this proposal introduces no runtime cost. Everything can be solved during compile time.

Compilation time: this proposal should not add a relevant burden on the compiler. “Everychange variables” lives and dies within a function; even a trivial implementation where the compiler makes a list of triggers and checks the whole list on every assignment within the same function should be very fast.

Code reading: good functions should not be extremely big in the first place, and thus programmers should spot very easily the presence of everychange. Forcing “everychange variables” to have a different naming convention makes sure that it's immediately clear which assignment may trigger a code. The name convention also makes sure to highlight the boundaries of the trigger. The same-scope restriction makes sure the triggers could not be abused.
Overall, one of the main goal of this tutorial is to improve code reading by reducing repeated code.

Language complexity: compared to other proposals, this one introduces just 1 keyword and a clear path on how to handle it. The keyword itself is very powerful and could be used to handle basically every error handling scenario. Other proposals, for example, introduce two different keywords for handling recoverable/unrecoverable errors; with this proposal you can just use two different variables, i.e. #err and #fatalError, and attach them to different everychange clauses.

Implementation: the implementation is not trivial, as it will require several changes to the compiler and pretty much every go tool.

Changes to Go ToolChain

Pretty much every go tool would need an update to handle the new keyword.

Performance Costs

No run time cost; negligible compile time cost.

Prototype

No response

@lucafabbian lucafabbian added LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal labels Aug 22, 2024
@gopherbot gopherbot added this to the Proposal milestone Aug 22, 2024
@lucafabbian
Copy link
Author

Nevermind, I just discovered that a similar idea I was not aware of has already been discussed in 2020. Thank you AI bot!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
gabywins LanguageChange Suggested changes to the Go language LanguageChangeReview Discussed by language change review committee Proposal
Projects
None yet
Development

No branches or pull requests

4 participants
0