Skip to content

Instantly share code, notes, and snippets.

@infinisil
Last active July 28, 2023 18:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save infinisil/b1b62cd9b7bb726aad5732fba332bd20 to your computer and use it in GitHub Desktop.
Save infinisil/b1b62cd9b7bb726aad5732fba332bd20 to your computer and use it in GitHub Desktop.

let with

Originally described in NixOS/rfcs#110 (comment)

{ pkgs, hello }:
let
  with pkgs;
  myHello = hello.override { ... };
in [
  myHello
  z3
] ++ let with pythonPackages; in [
  z3
  requests
]

Which gets desugared to:

{ pkgs, hello }:
let
  inherit (pkgs) z3 pythonPackages;
  myHello =
    if pkgs ? myHello
    then throw "myHello exists in pkgs, use a different variable name and verify its uses"
    else hello.override { ... };
in [
  myHello
  z3
] ++ (let inherit (pythonPackages) z3 requests; in [
  z3
  requests
])

And refined in NixOS/rfcs#110 (comment):

The main problem was brought up on Discourse: That changes to one file could break the let with in another file, for example:

Say you have

foo: bar:
let
  with foo;
  inherit bar;
in bar + baz

# desugars to

foo: _bar:
let
  inherit (foo) baz;
  bar =
    if foo ? bar
    then throw "bar exists in foo, use a different variable name and verify its uses"
    else _bar;
in bar + baz

If you first write code and test it with a foo that doesn't contain a bar attribute it works. But in a next iteration it breaks because you run it with a foo that does have a bar. This is unexpected because whether a variable name is valid and usable should be known at parse-time, not eval-time.

Note that the alternative of desugaring to

foo: bar:
let
  inherit (foo) baz;
  inherit bar;
in bar + baz

has the problem that the user might've wanted to access foo.bar, not the bar in scope, and there's nothing warning them of that.

The only alternative I can see working is to use a warning instead of an error, this way you can't break code, so desugaring to this:

foo: _bar:
let
  inherit (foo) baz;
  bar =
    if foo ? bar
    then builtins.trace "warning: bar exists in foo, use a different variable name and verify its uses" _bar
    else _bar;
in bar + baz

I don't see any problem with such a let with idea: It's statically analyzable, can fully deprecate with, warns you about any ambiguities, and is not overly verbose.

@inclyc
Copy link

inclyc commented Jul 28, 2023

Forwarded from the matrix room:

Another question about the new syntax: I have see many "desurgaring" in the RFC. Are you expecting that it should be addressed in the parsing state, or nix::Expr::bindVars?

@infinisil
Copy link
Author

This proposal (and I think the others too) would be done entirely during parse-time, aka syntactic sugar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment