-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Proxy like abstraction for an attrset #8187
Comments
This is a potential solution for
Not memoizing could be a feature, as it allows the result to be garbage collected, potentially. mkDynamicAttrs = builtins.mkProxy {
lookup = builtins.memoise key: "Hello, ${key}";
}
The difference would be asymptotic, as many packages use the same dependency. Upper bound may be exponential in the depth of transitive dependencies.
Many accesses are through
Supporting attribute names that are not in the list is of course not so nice. I think we might have a need for generic functions to distinguish between two classes of proxy attrsets: those that are properly enumerable (but may be expensive) and those that are not. Perhaps when I think multiple dispatch in It also opens the door to other features such as
|
A portion of this is implemented in master...flox:nix:dynamic_select I’ve got a continuation that implements this with a finite and infinite setting. Called it a “getter” rather than a proxy, but I don’t have a strong opinion on the name.
As @roberth points out, this in combination with a memoize primitive can be quite powerful. I had used the double-underscore pattern as this seems similar to the functor and rec-overrides usage. Another approach could be to make this construct memoize by-default and then it can be used to create a general memoizer out of it. |
If implemented as a primop that returns a sibling of
This would restrict the memoizer to strings only, and disallow string context. This approach reduces the potential of the memoizer and the proxy (by doing too much), so I'd rather have the separation of concerns. Regarding naming, |
So we'd have normal attrSets, recursive attrSets, dynamic attrSets. Very similar, but perhaps sometimes unclear which one you are working with. ie: given an attrSet, which type is it? |
I don't think recursive is quite the right name for #4154, because we already have Let's call them
You don't have to care about this when they have the same semantics. In order of increasing scariness: Caching attrsets should have no reason to behave differently. If we have lazily merged attrsets, either
Proxy attrsets can break the invariant that Although proxies are the scariest, I think they're more powerful and useful than lazily merged ones. If I had to pick one of the two, I'd go with proxies. |
This would be really nice for nix2data (terranix, nix2vim, etc) projects 👍 |
I think language bindings or an IPC protocol for evaluation would be more helpful for such applications, especially terranix. The proxy type discussed here does not interact with the outside world. Even the adjacent use cases for the implementation work that I've mentioned don't interact much with it. |
This feature would allow vtable-based method dispatch to be implemented transparently; a memory optimization that allows a different programming style. let
new = class: data: mkProxy {
hasAttr = k: class.methods?${k} || data?${k};
getAttr = k: if class.methods?${k}
then class.methods.${k} data
else data.${k};
}
Whether all of this is desirable, I don't know. The possible downsides don't seem so bad.
|
Another use case is functions like Evaluating a cleaned up tree of packages based on
A simple |
Another fun possibility (though probably not an ideal or recommended one) is an alternative to NixOS/rfcs#148 using the more familiar method chaining syntax. |
Given that a functor is an attrset used as a function, then a proxy should be to a function used as an attrset. Therefore I think the follow syntax would make sense: myProxy = __proxy:
if __proxy == "attrNames" then [ "name1" "name2" ]
else if __proxy == "getAttr" then (name: "Hello, ${name}")
else if __proxy == "hasAttr" then (name: name == "name1" || name == "name2")
else throw "Unsupported proxy operator" The argument name This implementation can be easy and effient because:
|
A nix library (maybe in mkProxy = vtable: __proxy: vtable.${__proxy} |
@roberth Do you think if it would help if I turn it into an RFC? |
lazyUpdate = first: second: __proxy:
if __proxy == "attrNames" then
lib.lists.unique ((builtins.attrNames first) ++ (builtins.attrNames second))
else if __proxy == "getAttr" then
second.${name} or first.${name}
else if __proxy == "hasAttr" then
second ? name || first ? name
else
throw "Unsupported proxy operator" |
@Atry Do I understand correctly that you're proposing not to change the language at all, or to put it in a I'm a bit concerned about the use of bare functions
Considering lucasew's motivating use case, a goal should be to make it as transparent as possible, so that a well-behaved proxy can be used in place of an equivalent attrset safely. I believe an extra conditional jump is worth the cost, especially if we'll have other uses for it, such as attrsets that are backed by an evaluation cache. If we don't want to touch the hot path, we could probably still implement this in |
I was proposing to change the language but only in the "unhappy path", for example here. I understand your concern and I agree they are valid points when desigining a new language. Here I more value precedents and backward compatibility than transparent behavior. nix-repl> builtins.isFunction { __functor = self: x: x; }
false |
I think as a pure functional language, evaluation cache can be decoupled from the internal data type. The cache is not neccesarily backed by an attrset-like data structure in memory. I wonder if it is possible to memoize functions in SQLite. |
This is why
Possibly. It could also be done with a new thunk type, in which case
It is, with any key value store, but it is far from trivial. |
I don't think attaching special meaning to parameter binding names is a good idea since that's really awkward and also not done anywhere else in the language. This is also inherently a backwards-incompatible change anyway since the '.' operator needs to be extended to work on these new dynamic sets. If anything, I would use the __functor-like style of "attrset with special functions":
But I would also much rather have a function like builtins.mkProxy since that's a lot less awkward to use. |
Is your feature request related to a problem? Please describe.
Sometimes we pay a big cost evaluating stuff just to use a small set of what is available. A feature like this could reduce the impact for smaller evaluations.
Describe the solution you'd like
In the JavaScript world, there is a tool called proxy. Basically it allows you to create an object (like our attrset) that looks like a static object but any access or operation is handled by a function instead of a lookup.
Actually, the idea came from RFC 140. With this system, a lookup could be trivially implemented by a slice and sum of strings. A
builtins.attrNames
could be very expensive, though.A clear and concise description of what you want to happen.
A builtin function to create a proxy attrset. Example:
The keys could be memoized, like a evaluation cache. I have no idea actually about the difference in number of evaluation thunks for something like nixpkgs though.
Describe alternatives you've considered
(pkgs "package").outPath
maybeAdditional context
Add any other context or screenshots about the feature request here.
Priorities
Add 👍 to issues you find important.
The text was updated successfully, but these errors were encountered: