-
Notifications
You must be signed in to change notification settings - Fork 23
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
Fusion and stdlib evolution #310
Comments
I want to clarify several points - From "Fusion specific proposal" - If fusion is no longer shipped with stdlib, some modules would probably be need into stdlib, either in If a module is added in "versioning for specific modules", specifically proc joinPath*(head: AbsDir, tail: RelDir): AbsDir =
AbsDir(os.joinPath(head.string, tail.string)) I've done this for oswrap module, along with some additional improvements for usability. It was relatively annoying one to write, but the advantage is that is requires almost no additional maintenance (for lower-level implementation at least) and can provide an additional layer of indirection to be used on both And making |
I think that versioning for specific modules is slightly better than moving them to some other namespaces. There will be no such a thumb modules with just import/export and deprecated pragma after moving from one namespace to other and later some time writing an wrapper in the previous versioned module to support the previous API is a slightly better idea than waiting for everyone to switch to a newer API. |
So the top comment makes it looks like JS libs are massively costly to maintain, Almost maintainance free, just someone changing Maybe in the past too much stuff was put into 1 module for JS target, thats not good, If someone wants to maintain less code:
|
Correct. I've updated the RFC to be clear about that. |
I usually worry if these things are supported by all major browsers but fair enough. However, Fusion wasn't created in anticipation of specific JS wrapper code. Nothing wrong with using Fusion for unexpected things, but it's time we update its official goals then. |
Why not split Fusion into several parts which can be handled by different versions of Nim. |
Proposal: Layered stdlib
1 new compile option, possible naming:
N meaning
Negative numbers are reserved for Nim core devs only, experimentals, quick prototyping, etc. Normie users like me use positive numbers. Nim with Fusion can be If some day a big company needs just the the compiler, If needed someday a Is flexible to allow even more customization, you can have a layer of "Pure libs only" etc, you get the idea. Total Layers
Implementation
template stdlib*(level: static[int]; body: untyped) {.dirty.} =
## Enable `body` if the `currentLevel` is greater than or equal to `level`.
when getoptions() >= level: # getoptions() Reads "--stdlib:N" somehow.
body
func newCrazyUnstableProc*() {.stdlib: 2.} =
echo "Breaky weird code" # No need to change imports, etc.
|
This RFC is about solving real, existing problems. It's not about joking around. In other words your proposal is so bad that I think you mean it as a joke. (Good luck trying to write libraries against these various language dialects. Which solve nothing.) |
Good luck trying to write libraries against these various |
I think there are many mixed together, but related problems here. If it is just about "distributing mutually cross-tested files", I think fision is the simplest idea (though nimble may need updates to make it easier..Not sure). That can distribute far, far more stuff (with the necessarily more lax review standards). If it is about staging for new stdlib modules, then I say just stage them in the stdlib directly. Keeping them experimental initially should be just a per-module-name build-time switch or in-client-code pragma (like all other experimental nim language features). Then migration from experimental to more supported just means no longer needing that switch, and eventually the switch itself can probably be dropped (if anyone cares). If it is about versioning modules in the stdlib with backward incompat APIs, I think a judicious once-in-a-decade move from os -> os2 and strutils -> strutils2 is not so bad to get better APIs. Extreme care should be used in doing the new APIs. For example, maybe So, TLDR - A) first figure out experimental stuff, B) then use that for stdlib back incompat upgrades seems best to me. And distribute like 50-100 "important packages", not just a tiny set. Whatever the CI intends to keep working. Just my two cents, as they say. |
{ BTW, another reason I like std/os2 instead of std2/os is that there may be a good fraction of modules that can be kept back-compat literally almost forever. So, maybe nothing needs to change for them. OTOH, I guess people could be confused by std/md5 or std/base64.. } |
Here are just a few problems that I don't see resolved with this
This is my approach: https://github.com/disruptek/dist It's not very well described in the README, but this is a fine place to answer questions, I think. It solves all the problems above and in particular, it lets the compiler depend upon any code in the distribution, should it so choose. As stuff moves out of We don't need to burn cycles testing the same code every time we want to test a compiler or stdlib change, and those test costs are more broadly spread across the contributors. Dropping code is as simple as not including it in the next release branch of the distribution -- users can still pick it up via a The Users can realistically use Nim as a scripting language with unqualified imports of unversioned modules and, regardless of which Nim version they use, they can expect the code to work. No package management necessary. You can reasonably use an older distribution with a newer Nim and you can roll this stdlib backwards and forwards as you see fit -- even individual modules can be adjusted in-situ. You can maintain your own fork of the stdlib trivially, if you so choose. You can even trivially include a specific version of the distribution as a requirement in your project. It's trivial to run a "sparse" distribution in which you only download the portions of the ecosystem that you actually need. Similarly, an Of course, sparse distributions still have all the tests and documentation for their modules and a package manager like Nimph can tell you which versions of the distribution pass the tests in your project and it can tell you whether a future version of a distribution module will break your code. A distribution manager like gitnim can coordinate switching distribution versions without any duplication of data, synchronizing your distribution version to that of the compiler or providing concise logs on what has changed and where. That should be enough to prompt discussion... 😉 |
@disruptek to re-iterate on some points that are not entirely clear to me (maybe I just misunderstood something, but I decided to clarify everything anyway), how following scenarios should be handled (assume "is that correct?" ending for each point):
|
Yes, but there's no reason to deprecate a module in the standard library unless we want to move it into a package where it can evolve. Shadowing the stdlib with an external package is probably the lesser of two evils. This concept doesn't demand that we break anything and there are no compiler release stipulations or timeline expectations; remember that all the packages in the distribution currently assume the standard library exists as it always has. If you really want to completely divorce the standard library, including all existing modules, we might be able to simply replace the stdlib with the distribution directory, but it doesn't seem to buy us very much. |
Given that whatever I wrote above represents my current understanding I second @disruptek 's proposal for stdlib evolution. Already mentioned |
I keep forgetting to mention this, but because |
Hmm, maybe I don't understand why it's needed, I don't like the idea. I don't need the distribution with arbitrary choices like html parser included/not-included, choices that are totally irrelevant to my use cases and needs. And after a while those "chosen one" libraries in the distribution will be probably outdated - and better, fresher, alternatives will be available as a separate third-party modules. I would prefer 1) small and clean Nim core 2) minimal std libraries included 3) simple ways to install separate libraries. As for the evolution of std - I'm willing to accept reasonable amount of breaking changes once say every 6 months if they make std and Nim better. |
Sure, but I don't. And plenty of others don't either, dependencies from multiple different sources have their own downsides. It's also not the topic of the RFC. |
So we can see theres people that needs a batteries-included stdlib, and people that just needs the compiler, |
If a stdlib module doesn't suit you, don't import it. Simple as that. No need for layers. There are also plenty of tests in Nim's repository that don't affect you, should we remove them too just so that we can indulge in some mad quest for minimalism? |
Another possibility for life cycle management from non-existent to experimental to supported to deprecated to non-existent again would be a |
Until I got to @disruptek's proposal I was thinking fusion would split into latest and stable packages. Test the former and latter against the current devel and 1.2 or whatever with stable. I was also going to suggest a concept of collections to help manage the subdivisions such as those seen in JS. A collection would be a set of modules that follow rules above and beyond the core fusion rules. Any module or library claiming to be part of a collection must meet those criteria. To borrow the JS example, one can have core js collection, a dependent on core would be DOM, followed by browser. One could also have a node module depending upon core. Finally one could create a collection with election/nwjs/etc modules. The standard library already covers some of the JS stuff that I mentioned, I'm just using it for illustrative purposes. The hope is that this reduces the number of rules fusion needs to have as a baseline, but can then have tighter guarantees on subsets. People also get some gentle guardrails to ensure they aren't too quick in pulling in one more dependency and reducing applicability. The implementation can start of as informal until a few collections and their rules become more clear, then those policies can be automated/tested. With all that said, dist would be a big step forward and doesn't preclude anything. |
Actually At the same time |
Package managers exist to solve this problem: You get a staging area, distro and all the other features. Critically, expectations are also managed for when packages can be deprecated: when nobody that imports them wants to pay the price for maintaining them. Core maintainers feel like maintaining batteries that go with a particular Nim version that are upgraded before releasing nim? Just create a @disruptek 's For example, we have two protocols that use This is generally solved by treating versions as separate packages - once you start doing this, life becomes much easier - upgrades can be isolated to where they are needed, instead of upgrading a giant monolith and risking the introduction of new bugs just because you need a small feature. This in particular is what keeps status from upgrading nim for example: we get all the new bugs for things we don't want to upgrade together with the things that we actually can use. Nim in particular is hard to keep compatible due to the multitude of unfinished features which means that even "bugfixes" change core semantics of the feature, but also due to how the module system pollutes the global namespace: generally, adding any public symbol is a breaking change. Ruling out package managers and keeping the above in mind, a poor-man's way for the standard library to evolve is to add versions of the same module - Another set of conflicting goals is to lower the barrier of entry for random people to add packages and at the same time maintain stability, compatibility and room for growth. Every time a new package is added, a new blocker for upgrading everything else is also added - people create packages at difference cadences, upgrade them and maintain them based on their own needs, not based on the needs of the Once there's enough people owning a Just because something is maintained outside of the standard library doesn't mean it cannot have stability guarantees - https://tokio.rs/blog/2020-12-tokio-1-0 is a good example of how a critical library can provide their own set of guarantees and evolution path, even for "important" features - good code survives regardless where it lives - the standard libarary and related stability guarantees are unique in that they promise to keep the bad code and bugs around as well. |
In the end, the reality is moving files from one location to another wont fix bugs, |
My proposal is: simply get rid of Compare the situation with other programming languages that have modules. Like for example Python. There is no "std.re" module, it's simply "re". There is simply no need for "std.re" or "std." in the first place. "re" alone is all you need. The "re" module can be shipped together with the python executable. This will make it a standard module, not the module path or the name. If someone uses So of course the "re" shipped will use the features of the shipped binary. And of course a side-installation of "re" could either be an older one (e.g. to have compatibility with code), or a newer one (where the programmer makes sure by himself that it works with the older binary). Having to rename "from experimental import re" into "import re" or "from std import re" is then simply not necessary. Also, needing to change "from std2 import re" to "from std import re" should also never be necessary. |
Nim mostly does unity builds, having a difficult to resolve conflicting versions inside a single Nim project (Nim project source file + config) seems like something that's at a scale that most are not going to approach any time soon, maybe I lack imagination. I'm wondering if the criticism needs adjustment as it might be in the category of perfect is the enemy of good enough. Nim has many tools that make the situation seem far more tractable than in other languages:
At that point the mostly syntactic but ultimately surface level incompatibilities that turn into big show stoppers in other languages are potential not in Nim. The rest are harder to resolve issues:
|
The criticism is fair and the problem is real, but it's only made easier by When I made Nimph last year it was originally going to handle diamond dependencies automatically, but I was eventually convinced that pushing users not to create these scenarios in the first place was the wiser move. The point is, Nim was capable of handling the situation then and according to a test @Clyybber did the other day, it still is.
This is a pretty major predicate with which to defend the
You don't have to upgrade Nim; that's a business decision that no one here is proposing to make for you. I honestly don't know why you would bring it up.
I agree, but this is far from a novel problem. Happily, there is again a solution in versioning. There are some plans to vet API changes automatically to help promote versioning as well, but it's largely a social problem as I'm sure you know.
I really don't think random people adding random packages reflect the reality of a standard library, be it one that ships with the compiler or one that is distributed via git. We already have the means to share code "randomly" via package managers, git, and floppy disks that you find in the IKEA parking lot.
I think the point you're making is that a new package may be added that depends upon a version in The solution is obvious: fix the new package or drop it in a subsequent release. I hope that Nim is released more often in the future, but in any event, this is a social problem and one that If I die of COVID tomorrow and @Araq wants |
I have never used or understood what fusion was. Maybe heard about it once or twice. I think fusion really has failed to market it self... I fully support @holgerschurig proposal. Keep it simple. Don't do fusion, don't do std2. Just decide if a module is good enough to bring standard lib or not. |
for small personal / toy projects, sure - but then you run into scenarios where you don't fully control transitive dependencies or managing them is costly and above all, unnecessary - if project
Which situation?
No, we don't - but it flows both ways unfortunately - we can't contribute to Nim either because if we don't upgrade, we don't benefit from the fixes - the larger and more unwieldy Nim is, the harder this becomes.
versioning is a social solution indeed to inform your downstreams what level of compatibility they should expect - but for managing transitive dependencies there exist technical solutions as well (namespacing) that allow two "versions" to be used concurrently without issues.
well, it's part of the RFC, else I wouldn't bring it up, really.
well, that's the point a bit: if you promise to keep things compatible, you assume responsibility for putting in the work, or your promise is empty - this tends to be the rub - it's hard to curate such sprawling collections of work which is why it often breaks down after a while - you're rarely an expert in each of the fields so either you have to coax upstreams to upgrade with you, do it yourself or indeed drop the package - if someone's willing to do that curation work, sure, it's a fine option that might suite some use cases, but there are several problems that we're having (in practise) which |
Call it whatever you want; the point is that there's no technical obstacle to supporting it. Please stop referring to projects as "small", "personal", or "toy". It's insulting. I promise you that the smallest personal project is no less important to the author than the largest codebases, and it's certainly much more important to Nim to win such customers than it is to satisfy a smaller quantity of entrenched users.
What contribution are you referring to? This RFC is about the stdlib evolution. As far as I can tell, recent contributions from Status to the stdlib have been minimal at best, and in fact I would call most of your work destructive in that it has forked attention and support in the ecosystem. Cue @Araq coming in to tell me to shut up.
Someone has to do the work to curate the stdlib now, in case you hadn't noticed. Those who have signed up to help on
I went looking for the 5,000 line Nimble PR to add lockfiles the other day and couldn't find it. Do you have any contributions to announce on that front or is your message merely that |
Ok, thanks for all the input so far. The RFC is rejected and we will accept better stdlib modules under new names so that nobody notices the stdlib is living software where some kind of versioning/evolution is going on. |
None intended, sorry if it came across that way. Some issues become apparent only once a project hits a certain size though - or rather, once enough projects / packages exist that you want to start mixing and matching them more freely - whatever solution is chosen for the standard library would do well to take this dynamic into consideration.
indeed, this is the situation I'm trying to highlight: we cannot practically contribute to the standard library because we cannot a) wait 6 months for the fix to be released and b) find it more and more difficult to upgrade due to the number of unrelated changes inadvertedly affecting our codebase - a smaller standard library and a more dynamic approach to packaging would help, as would the separate namespacing strategy overall so that we can upgrade in a more granular way - the question the RFC poses is whether this should be done as a grand |
"Fusion specific proposal" - in the main RFC message you mentioned that |
I don't know. Either we leave things as they are now or we move some essential Fusion libs back into the stdlib. Even Rust and C++ have atomic refcounting in their stdlibs. |
I think fusion is comparatively adequate idea in general. I think it mainly feels failed because:
More on The fact In the end I don't think it is necessary to deprecate it, or come up with different solutions, but having a little more attention directed to it would not hurt. PS: if my understanding of how fusion is packaged is correct I can make PR with fix, and update documentation. Whatever I wrote above is just a specific example that in my opinion illustrates quite well why it feels failed in particular. |
I feel like a broken record but I am convinced that to limit friction the standard library should:
This decouples the interface from the implementation. Then I'd like some way to evolve the std implementations. Because most of it was implemented when nim was very young and they should be reevaluated, but the first step is agree on the interface and then the implementation can be rediscussed. Case in point, bitops has problematic implementation on countLeadingZeros that requires me to reimplement it for every single project I build, in particular adding a BigInt library to Nim will require to fork bitops nim-lang/Nim#14696 |
@mratsim I am listening but these things take time and Fusion doesn't touch any of these things. Also, these "interfaces" can be harder to design and program than the actual implementation. Also, the assumption that you can switch between these different implementations is naive. For example, even though our tree-based Table implementation has the same API as the standard Table, we cannot change the standard table implementation easily -- the tree based impl cannot be put into a |
We can also fix these without having a grandiose design for async. |
We can fix them, but we need to agree on ways to evolve the standard library.
Yes that's true in particular for complex things like streams or serialization, hence why we need guidelines on how to evolve the standard library. But for both tables or even more so for say channels or iterables, there are a restricted number of ways to use them. Whether an implementation can work at compile-time or not is not a problem as long as we can choose one for our use-case. For example, one channel might not be suitable for use-case A because it requires multiple producers, well fine, there is another implementation that provides this. Use-case B wants single producer single consumer and highest perf possible because it's audio, perfect there is one channel for that. |
Fair enough; I shouldn't have let it raise my hackles. Damned hackles. The problem of code reuse is, in my opinion, mostly due to the complexity exposed via configuration and package management. A smaller project with many dependencies effectively has the same problems as a larger project with many dependencies. But this is the problem that
Good point. Are you willing to point at a stable git reference for Status packages in |
I do not understand the concrete roadmap of this, is it going to be archived?, is it going to be moved to stdlib?, deleted?, something else?. |
I don't know that it's a flaw so much as a difference in requirements as there are obviously projects that have different priorities and issues than ours - the principal difficulty we're facing is that we want to upgrade things independently and in smaller units, and not always to the same version across transitive dependencies - coming back to the "minimal supported version" strategy, it is more or less what we pursue at certain points of our roadmap: we don't necessarily always want the latest version of everything, but rather upgrade specific components on a case-by-case basis. For example, in our applications, reading JSON files right now is a fringe operation - it happens sometimes, but unless there's a security bug, it doesn't really matter if an optimization makes it 5% faster - the risk and churn of upgrading the JSON component doesn't motivate an upgrade, but does represents a cost: we must now audit the new code for security issues (as we've done with the rest of the codebase, every dependency included). Conversely, we might upgrade something very quickly when a bug is found that affects our users, specially if it involves remote exploits and this is, above all, the time that we don't want to be receiving unrelated changes. Ergo, by and large, having a JSON parser in the standard library is mostly a cost, but almost no benefit for us - a standalone library serves our use case a lot better. This difference in pace and priorities repeats across many of the libraries we work with, though it's not always titled in the conservative direction, of course. A As much as |
Again, and I can't stress this enough, I don't care about you. We've already established numerous times that Status has a use-case that is dissimilar from the rest of the community. You guys don't even use our package managers, as far as I know. You are going to set your requirements on your own terms, and that is appropriate. What you do is your problem, and I've long given up expecting much contribution from Status to the community at large. That said, the idea here is that by putting modules into a monorepo, we benefit from more users, more eyes, more development. The community can provide more value to Status, and vice-versa, without pissing in your precious walled garden. If you still don't see the value proposition, I urge you not to participate. I'm sure someone else will take the role on your behalf, which would be ideal.
Sounds like you'd be happiest if the standard library didn't exist and everyone just used your code so security bugs could be found and fixed at a faster pace. That is exactly the goal of
Tell me, do you find more bugs with fewer users or fewer bugs with more users?
So you support moving packages from the standard library to, literally anywhere else, as that reduces your costs. Got it. Sounds like
You. 👏 Your participation would be one of marketing, usage growth, exposure, et cetera -- and ensuring that the rest of the community doesn't do something stupid that causes unrelated software to develop dependencies that later come back to prevent co-use with your golden goose json serializer. Unacceptable parts censored. |
Background story
We tried to move PMunch's excellent socket stream implementation from a PR against Nim's stdlib to Fusion. This PR needs the new
cast(tags: [].)
construct and since fusion currently aims to support Nim version 1.2, we cannot make it available there easily. This means even though Fusion is supposed to be a staging area for the stdlib, contributing to Nim's stdlib is easier than contributing to Fusion. Clearly against Fusion's design. At the same time, Fusion is growing JS specific libraries and other helpers which don't follow our idea what Fusion should be about. Nothing against more JS specific wrappers, but Fusion aims to support its code for the next decade and JS wrappers are a poor fit for it.At the same time, we don't need yet-another Fusion-like repository just because Fusion is used differently than we anticipated. And there are more problems with Fusion itself too: It ships both with the latest stable Nim and is installable as a Nimble package -- whenever we offer a choice we have to ensure that every choice does work and keeps working. Hence offering this choice is a bad idea.
Fusion specific proposal
Make Fusion broader in its scope. Do not ship Fusion with Nim. Fusion should target the latest stable Nim and also work with Nim devel, but not every module in Fusion needs to support eg. Nim version 1.2.
If fusion is no longer shipped with stdlib, many modules should make it into the stdlib, either in std2 or in std. Obvious candidates are: smart pointers, tree based collections, the pattern matching macro.
Proposal: Introduce a
std2
namespace for Nim stdlib modulesstd2
namespace as an alternative to anexperimental
namespace. The problem with the "experimental" namespace is that it's not clearly defined when things are not "experimental" anymore. Also, designing an API for eg. rational numbers is much easier than designing new macros for async, yet every new module should start out as "experiemental", somehow.
And it gets worse: If we later move
experimental / module
tostd / module
when the module is not experimental anymore, we break code. So for backwards compatibility reasonsexperiemental / module
has to remain! Let's assume we introducestd / module
and then makeexperimental / module
refer tostd / module
, like so:But now we created a situation where it's better to import experimental / module! Because that's the import that actually works with older versions of Nim! So client code becomes:
But only for the code that was updated, other old code simply uses
import experimental / module
without thewhen
statement, which is ugly anyway. For these reasons it is much better if new, experimental modules start in thestd
namespace. Or in a newstd2
namespace. Whether it's experimental or not can be mentioned in the documentation, but it is expected that everything instd2
becomes non-experimental with the release of Nim 2.0.Alternative proposal: Introduce versioning for specific modules
There is some desire to evolve core libraries further:
os
, we like to have more type safety in the form oftype AbsoluteDir = distinct Path
.strutils
we like to use more ofopenArray[char]
building blocks, plus we like to split it up into multiple, smaller modules.io
some basic streaming abstraction that works with async might be a good idea.However, these outlined changes cannot be applied to the existing os, strutils, io modules without severe interruptions for the people using these libraries (which is effectively everybody who uses Nim). So we can either have
std2 / os
orstd / os2
or maybe evenstd2 / os2
.Considered, rejected alternative
An alternative that I considered was to come up with slightly different names like
input_output
instead ofio2
etc. That's worse because the connection between modules namedio
andinput_output
is unclear. There is also no clear way how to name the 3rd version of io then whereas a name likeio3
is obvious.The text was updated successfully, but these errors were encountered: