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

docs: add variables; rework scope #11062

Merged
merged 2 commits into from
Jul 31, 2024

Conversation

rhendric
Copy link
Member

@rhendric rhendric commented Jul 8, 2024

Motivation

Part of #10970.

Starting in on formally specifying the language grammar. Semantics of variables and scopes are very closely coupled so I did the two pages together.

Some language here is driven by the existing error messages. I might have called ‘variables’ ‘identifier expressions’ instead, and I might have called ‘definitions’ ‘bindings’ as they were previously documented. But if you try to evaluate xyzzy, the error message says undefined variable 'xyzzy', so I figured it'd be better to standardize on those words. (Plus, not calling scope entries ‘bindings’ will make it less confusing when I start talking about ‘binds’ as the things that let-expressions and attribute set literals contain.)

There are, of course, a few equivalent ways to describe how scope and name lookup works. I went with the one I think is simplest, but I'd like to get agreement on this early, because it'll affect how I explain things like call-by-need evaluation later.

Priorities and Process

Add 👍 to pull requests you find important.

The Nix maintainer team uses a GitHub project board to schedule and track reviews.

@rhendric rhendric force-pushed the rhendric/reference-manual-2 branch 2 times, most recently from e9e33c7 to 2bef8c1 Compare July 23, 2024 17:03
@rhendric rhendric force-pushed the rhendric/reference-manual-2 branch from 2bef8c1 to bea9e3d Compare July 25, 2024 02:08
@rhendric rhendric changed the title docs: add variables and identifiers; rework scope docs: add variables; rework scope Jul 25, 2024
doc/manual/src/language/scope.md Outdated Show resolved Hide resolved
doc/manual/src/language/scope.md Outdated Show resolved Hide resolved
@@ -1,14 +1,34 @@
# Scoping rules

Nix is [statically scoped](https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scope), but with multiple scopes and shadowing rules.
A _scope_ in the Nix language is an [identifier]-keyed dictionary, mapping each identifier to an expression and a _definition type_.
The definition type is either _explicit_ or _implicit_.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this distinction is over-complicating things. We can merely list definition types and add the details in place instead of referring to more details and potentially having to duplicate information.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what you're proposing here. If you meant to mark this line, what alternative would you propose for listing definition types, and where is ‘in place’?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just list the definition types directly without classifying them into explicit and implicit, and explain the semantics of each in their own respective section/page.

Copy link
Member Author

@rhendric rhendric Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Perhaps I've written this confusingly.

The thing this paragraph is trying to express is that we have (conceptually):

enum DefinitionType { Implicit, Explicit };
using Scope = std::map<Name, std::pair<Expr, DefinitionType>>;

It happens to be the case that the expressions that extend scopes can be classified into implicit and explicit, but that doesn't matter for purposes of defining what data a scope contains. A hypothetical Nix language extension could allow let-expressions to create both implicit and explicit definitions, for example:

let
  a = 1;
  implicit b = 2;
in ...

But there is a meaningful difference between the scopes $\{ \texttt{"a"} \mapsto (1, \mathit{implicit}) \}$ and $\{ \texttt{"a"} \mapsto (1, \mathit{explicit}) \}$; the difference is revealed by the fact that with { a = 2; }; a evaluates to 2 in the former scope and 1 in the latter. So the difference should be explained in the description of what a scope is.

The previous version of this documentation achieved this goal by partitioning scopes into primary and secondary maps, like so:

struct Scope {
  std::map<Name, Expr> primary;
  std::map<Name, Expr> secondary;
}

This is isomorphic to the other Scope type, but when I tried explaining the concepts ‘primary’ and ‘secondary’, I noticed that they don't really connect to the language we naturally use to describe the special behavior of definitions created by with. We naturally describe those definitions as implicit, so I thought it would be easier to understand if I took the implicit/explicit distinction and applied it directly to definitions (map entries) in scopes.

(There's also the fact that the primary/secondary presentation allows for the same name to be a key in both ‘subscopes’, which is why the two presentations aren't isomorphic. This makes describing shadowing a little simpler, but makes describing variable resolution more complex (first check the primary subscope, then the secondary subscope). With the implicit/explicit presentation, all of that is handled in the description of shadowing.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understood what you were trying to achieve, but I think it front-loads a lot of complexity. IMO we have to design the manual for incremental reading, and putting too much operational detail into an overview will just destroy reader's motivation to plow through the rest. Therefore I'm suggesting working out the details in the respective definition types, and e.g. be appropriately more diligent when describing the semantics of with.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of this as a specification of what ‘scope’ means, not an overview.

There should be, somewhere, a reference-level specification of what a scope is, so that other places in the language reference can link to that when scopes are mentioned.

If not here, where should that specification go?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I gave it another thought, and yes, that's probably the best way to do it. Thanks for the productive discussion and sorry for the delay.

@rhendric rhendric force-pushed the rhendric/reference-manual-2 branch from bea9e3d to 180f16c Compare July 25, 2024 02:40
See [`with`](./syntax.md#with-expressions) for a detailed example.
Every expression is *enclosed* by a scope.
The outermost expression is enclosed by the [built-in, global scope](./builtins.md), which contains only explicit definitions.
The respective definition types *extend* their enclosing scope by adding new definitions, or replacing existing ones with the same name.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The respective definition types *extend* their enclosing scope by adding new definitions, or replacing existing ones with the same name.
The expressions listed above *extend* their enclosing scope by adding new definitions, or replacing existing ones with the same name.

Having just defined ‘definition types’ to mean the set {implicit, explicit}, I don't think it should be used in this sentence.

@fricklerhandwerk fricklerhandwerk merged commit 6ed67d3 into NixOS:master Jul 31, 2024
12 checks passed
@rhendric rhendric deleted the rhendric/reference-manual-2 branch July 31, 2024 21:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants