-
Notifications
You must be signed in to change notification settings - Fork 90
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
Handle non-local dependencies #329
Comments
I think, the second one can be tackled easily. About the first one, do you mean to have sugaring for the imports? something like: I suppose that the 3rd is not in the scope for a MVP. May be the 1st one neither. |
I had in mind something a bit more involved, allowing for example
It might be as it can be reasonably simple (not really more complex than the 2d approach) and it’s the kind of things that might be worth setting early-enough to make it standard. |
PreambleIn the following, PM is used for package manager. We explored the following possibilities:
This discussions revolved a lot around what should be the exact role of Nickel. We all agreed that an ideal solution would be a composable, lockfile-based solution. The problem is that package management is hard, it is a deep rabbit-hole that requires to figure out a lot of things upfront. It has been re-implemented so many times that would be sad to do it yet another time. What's more, one constraint is that we want to maintain a reasonable closure size as much as possible (for embedding Nickel in many workflows easily, to deploy the WebASM playground, etc.). Although not mentioned explicitly during the call, we morally based our assessment on the following criterions:
Option 1: full-fledged package managementOption 1. is complex. It requires a lot of time, design, implementation and maintenance burden. It could also increase Nickel's closure size, although the package management part would be in a separate tool. It's probably the most featureful and evolutive one, though. However, going for this one would mean not having even a basic mechanism for non-local dependencies for quite some time, until the system is designed and starts to be implemented, which can be prohibitive. Option 2: lockfile based resolution, offload package managementOption 2. is appealing, as it is still quite featureful and evolutive, while being less complex. A natural choice would be to use Nix/flakes as the PM, and have Nickel understand lockfiles directly. We can also define a simpler, PM-agnostic Nickel-specific lockfile format and a simple tool Using a PM-agnostic lockfile format though, users could use whatever PM they want, such as Option 3: include pathsOption 3. is something used by e.g. Jsonnet or even the good old C/C++. We think it doesn't really provide any advantage over 2., as it is marginally less complex, but has the disadvantage of making composability even harder, and risk introducing fragmentation if/when at some point we introduce a lockfile-based mechanism. Option 4: include URLSOption 4. is used by Dhall. It has the advantage of being simple, not having to handle dependencies, packages and all, although the impact on the closure size may be noticeable because of the requirement of an HTTP(S) stack. It is more bare-bone, as you can't really have a fine-grained management of dependencies nor an easy update: either you pin things via SHAs but update is painful, or you don't pin but this becomes brittle. We can still imagine having an external tool that handle the update, as importing from URLs with SHAs is in some sense a lockfile that is inlined in the source file. All in all, it could still make for a nice alternative for simple workflows while waiting for a better solution like option 1. or option 2. However, such a transition is always risky and may cause a lot of friction. One way to lower this risk is to make clear in documentation from the beginning that this is a transitory solution. ConclusionAt the end, our feeling is that going for option 4. while calmly figuring the details of option 2. is a reasonable trade-off. |
I think that only option 4 makes sense today. We don't really have an identified need for anything more complex today. When we know more about how people use Nickle, we can revisit the question. |
The Dhall experience seems much worse than NIx today, when it's really unclear what code needs network access. I do not recommend that. |
Maybe we want to ping @Gabriel439 on this, since they have the most experience with non-local dependencies in a config language. |
From what i gathered you really want a semantics where file So you need both a more complex notion of what “relative” import means, as well as a concept of cross origin security (CORS). |
I view the tradeoffs of URL-based imports differently: from my perspective URL imports provide a simpler user experience but they are not necessarily simpler to implement. In fact, they are actually the greatest source of complexity in Dhall and the thing that most Dhall binding authors complain about implementing. Specifically, URL imports simplify the user experience in the following ways:
However, URL imports actually complicate the implementation in the following ways:
You will probably also want to read Dhall's Safety Guarantees post, which covers the above topics in more detail. From the perspective of making your language integrate with an existing package manager (e.g. Nix or Bazel), URL imports with integrity checks can play the same role as lockfiles, meaning that you can generate code for an external package manager from URL imports if they all have integrity checks. See, for example, the Dhall integration for Nixpkgs, which explains this in more detail: The way I like to think of it is that Dhall technically does have a lockfile, but it's intermingled with the source code (in the form of the URL imports with their associated semantic integrity checks). |
Thanks @Gabriel439 ! This makes me think that there is a solution that we haven't been considering yet which is “just use Git submodules”. I'm not terribly fond of Git submodules. But they exist, which makes them quite a bit simpler than what we've been proposing so far. |
Two more points to consider:
|
Thanks for the hindsight, @Gabriel439. It sounds like we underestimated the URL route 😕
I've seldom used git submodules, but indeed that could be a (zero-cost I guess? There's pretty much nothing to do on the Nickel side for us?) portable solution in the meantime. In parallel, we could have some flake template/Nix library that makes it easy to distribute Nickel code as flakes, as it is highly likely that most of first adopters are using it. |
|
Forgot to mention: this is all in favour of option 2 above: reuse other package managers. |
I wholeheartedly agree.
On the other hand, it comes with a risk: fragmentation. Let's say you're a user, and you want to use a bunch of different Nickel libraries: that would be very frustrating if you have to install |
I agree that it would lead to some fragmentation, but I would expect people who mainly use Nix have Nix dependencies that provide some Nickel libraries in them (this is the case for
Defaulting to Nix has its downsides: not everybody wants to or can use Nix. It would be a shame if Nickel wouldn't work on Windows just because it requires Nix for packaging, for example. Also, it wouldn't be as hidden as one would prefer: locking would only be ensured if you write a proper flake, and use |
Ah, Windows is a very good point (that I tend to forget about 😅 ). I think my idea of a blessed package manager was to say: please use your own specific package manager for domain-specific librairies, but for pure Nickel library, we just made an arbitrary choice for you. Even in the future I would be sad if we have to re-implement a package manager for the thousand time, so I guess we mostly align on the idea but I wanted to offload the default package manager for pure libraries to an existing one. Maybe it should just be something different from Nix, then. One that is light and portable, if possible. |
I'm trying to implement my proposal and I hit a snag: while Nix treats What do you think about making Other options in the context of this issue would be:
|
Currently `import` is treated as a very special function that only accepts literal string as its first argument. It has following downsides: * Special handling of `import` is hidden. User can assume that its just a function while it's a special keyword. User might expect to be able to pass variables to it while it is only handled before typechecking and evaluation. * We can't extend `import` functionality without introducing new keywords which is not backward-compatible. This change makes `import` into another statement like `let` or `fun`, which means it cannot be confused with a function anymore. It also means that expressions like `import "foo.ncl" bar` and `import "foo.ncl" & {..}` are invalid, and `import` statement need to be put in parethesis: `(import "foo.ncl") bar`. For more context, see discussion in #329 (comment)
Currently `import` is treated as a very special function that only accepts literal string as its first argument. It has following downsides: * Special handling of `import` is hidden. User can assume that its just a function while it's a special keyword. User might expect to be able to pass variables to it while it is only handled before typechecking and evaluation. * We can't extend `import` functionality without introducing new keywords which is not backward-compatible. This change makes `import` into another statement like `let` or `fun`, which means it cannot be confused with a function anymore. It also means that expressions like `import "foo.ncl" bar` and `import "foo.ncl" & {..}` are invalid, and `import` statement need to be put in parethesis: `(import "foo.ncl") bar`. For more context, see discussion in #329 (comment)
Currently `import` is treated as a very special function that only accepts literal string as its first argument. It has following downsides: * Special handling of `import` is hidden. User can assume that its just a function while it's a special keyword. User might expect to be able to pass variables to it while it is only handled before typechecking and evaluation. * We can't extend `import` functionality without introducing new keywords which is not backward-compatible. This change makes `import` into another statement like `let` or `fun`, which means it cannot be confused with a function anymore. It also means that expressions like `import "foo.ncl" bar` and `import "foo.ncl" & {..}` are invalid, and `import` statement need to be put in parethesis: `(import "foo.ncl") bar`. For more context, see discussion in #329 (comment)
Fixed by #1716 |
Is your feature request related to a problem? Please describe.
The only way in Nickel to load some external code is by hardcoding its path (relative or absolute) in the nickel file. This is fine to refer to files inside of a project, but doesn't scale as soon as someone wants to refer to an external nickel “library”
Describe the solution you'd like
Have a mechanism to reference “external files” in nickel.
I guess designing the final shape will require a bit of work, but right on top of my head I can think of several approaches
-I
argument, or via aNICKEL_PATH
environment variable)nickel.lock
file (or whatever) that would specify how to map specific inputs to actual files. This lockfile could either be generated by nickel itself (but that seems quite out-of-scope for the language), or could have a public schema so that it could be generated by other tools (Nix, looking at you) or manually.The text was updated successfully, but these errors were encountered: