Problems related to `scopedImport`
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 17.7k
- Forks
- 2k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 80
Description
Confusing free variables
A bit more problematic is the fact that files called with a scopedImport have free variables, which can be confusing as to where they're actually defined, compared to a function argument at the top where it's clear that the variables are passed in by a function call.
This is in my opinion the main reason we might consider removing it.
Doesn't allow file evaluation to be cached
Here's an example where scopedImport can't cache files the same way import can. This is how evaluation is cached with import:
# function.nix
let
b = builtins.trace "calculating b" (1 + 1);
in { a }: a + b
# call-function.nix
import ./function.nix { a = 1; }
*
import ./function.nix { a = 1; }
Note how calculating b is only printed once:
$ nix-instantiate --eval call-function.nix
trace: calculating b
9
Trying the same with scopedImport:
# scoped.nix
let
b = builtins.trace "calculating b" (1 + 1);
in a + b
# import-scoped.nix
builtins.scopedImport { a = 1; } ./scoped.nix
*
builtins.scopedImport { a = 1; } ./scoped.nix
Now calculating b is printed twice:
$ nix-instantiate --eval import-scoped.nix
trace: calculating b
trace: calculating b
9
But this still makes sense if you think of scopedImport as just importing the file as the function body, prepending an attribute argument to it and then calling it, so like this:
# uncached.nix
{ a }: # This line would be prepended implicitly
let
b = builtins.trace "calculating b" (1 + 1);
in a + b
# call-uncached.nix
import ./uncached.nix { a = 1; }
*
import ./uncached.nix { a = 1; }
which will also calculate b twice:
$ nix-instantiate --eval call-uncached.nix
trace: calculating b
trace: calculating b
9
So I don't think these semantics are problematic, as long as the docs clearly explain them.
Very related is https://github.com/NixOS/nix/issues/6228 for caching function application.
Combined with the ability to shadow builtins
However another more fundamental problem is that builtins can be shadowed:
let
import = file: "Not importing ${toString file}";
in import ./file.nix
Which when combined with scopedImport can make all import's not be able to cache evaluation anymore:
# expr.nix
builtins.trace "Evaluating expr.nix" {
nested = import ./expr.nix;
}
# scopedImport.nix
let
scope = {
import = file: builtins.scopedImport scope file;
};
in builtins.scopedImport scope ./expr.nix
# normalImport.nix
import ./expr.nix
$ nix-instantiate --eval scopedImport.nix -A nested.nested.nested
trace: Evaluating expr.nix
trace: Evaluating expr.nix
trace: Evaluating expr.nix
trace: Evaluating expr.nix
{ nested = <CODE>; }
$ nix-instantiate --eval normalImport.nix -A nested.nested.nested
trace: Evaluating expr.nix
{ nested = «repeated»; }
This will automatically become impossible once we fix the problem of being able to shadow builtins, so it's not a problem with scopedImport specifically.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the builtins.scopedImport behavior described in the issue and reproduce the nix-instantiate examples for caching and shadowed builtins. Read the related issue #6228 for context on function-application caching. Work is complete only after a decided direction for scopedImport is implemented or its semantics are documented and covered by appropriate tests.
Written by the indexing model from the issue text.
Assessment
- Domain
- tooling
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100