-
Notifications
You must be signed in to change notification settings - Fork 441
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
Deconstruction support for locals #307
Comments
A simple workaround is |
I'm not sure I understand. It seems like that makes each line smaller (e.g., Does that make sense? |
You wouldn't define |
I think there are definitely scenarios where something like this would work (personally, I would prefer |
Variables are supposed to be statically bound, so the "import * from" would defeat that. Code like
might break if somebody added x to foo.libsonnet. Our Python style guide prohibits the import * idiom. There may be a case for syntax sugar to reduce the size of this though:
I.e. without the repeated core.v1 and without the locals. In fact you can already drop the locals:
To drop the other repeated parts, you could have
This is like what you said, except I'm re-using the existing keyword 'in' and there's no need to tie it into the import syntax, since it might be useful independently. You can always do:
|
Would JavaScript destructuring syntax work? local { container, claim, … } = core.v1; |
Yeah it's preferable to use an existing syntax if one exists. That looks like it shouldn't clash with anything, although it's a bit weird to see { } used for a sequence instead of [ ] |
Technically, it's not a sequence. In JavaScript, {a:1, b} is shorthand for {a:1, b:b}. Destructuring assignment reuses the same pattern. |
I think the |
I think But any change to local should include both the local x = e; form and the local x = e, form (i.e. the one inside objects). Also, the mutually recursive form should work too, i.e.
should evaluate to 2 We need to decide whether the object on the right hand side is allowed to have more fields than the one on the left. My instinct is that it should be allowed to have more fields than you're pulling out. Does Javascript have those semantics? Although it's not so hard to implement, I'm not going to have time to do this any time soon. But if you want to, the steps are:
The actual desugaring is going to be somewhat complex. For the general form:
It's not strictly necessary to factor out the expr1, but in the case where it is expensive, it will only be executed once if you do it as so: local |
If you're going to go down this path, several elements could be considered in addition to the above suggestions:
You could also allow attribute shorthand in object-construction, e.g.: All the above together would be fairly ambitious, and in some cases the cost-benefit wouldn't be obvious. So I'd propose starting simple, but keeping the above in mind so that decisions made now don't exclude future possibilities. I'd love to tackle the base case, so if no one else plans to work on this, I'll take it. |
Hmm, I'm not in a position to sign a CLA. I'll follow it up with my employer, but I wouldn't hold your breath. Nothing moves fast at a bank. |
This is still on my radar, and I'm working on getting an IP release from my employer in the next week or two. Meanwhile, I've implemented the basic syntax: I'm currently looking at remainder syntax: |
Thanks for the update. Does It's actually a bit tricky to preserve the hidden-ness of fields right now because you can't do Implementing ...r would be a lot easier if we had an explicit way to delete fields. I would punt ...r for now. It's not being asked for in the original request from @hausdorff |
I haven't started on
I also haven't thought through how I'll hold off implementing |
Ignoring the :: in the deconstruction sounds good to me. (Referring to |
A way to delete a field (without ::) is something people ask for from time to time so I think it's better to just add that. Then you don't need any elideFields call that then has to preserve the : and :: |
I finally got CLA-signing approval (as you'll have seen on go-jsonnet). I've put together a commit for basic destructuring assignment as described above (still leaving out The first issue is that, while implementing this simple change, exclusively for The second issue occurred to me when I started thinking about how to implement function-parameter destructuring, and I realised that the approach I took for the basic implementation doesn't readily extend to the other cases. As it was, even A nastier third issue came to mind with respect to desugaring. It appears that each construct would require quite different desugarings. Functions might be straightforward, since Another thought was to extend the core language to support |
With all that in mind, my current plan is to start again and do the work in multiple phases:
Thoughts? |
Sorry I only just saw this. I need to look closely at your work but I will say now that local in comprehensions has come up before: #90 |
Also, the go-jsonnet desugaring of comprehensions is completely different to the C++ one. |
Sorry it took me so long to get back to you on this. To your first point (whether we need to do it everywhere a binding occurs), I also prefer it to be symmetric as that's less surprising for users. However we could definitely stage this and I think the most important case right now is importing specific things from a library, so just the When you desugar the object locals, you can expand them to a local within each field that still has the destructuring sugar in it, then when you recurse the desugaring on those expressions, it will use the code for the regular local desugaring to eliminate the destructuring. In other words, the desugaring code need not be duplicated between the two. Other than that, you're talking about the feature cropping up in parsing, desugaring, and unparsing, twice in each. While there is a bit of duplication, it's somewhat minimal and you're able to make use of parseParams and unparseParams so it's fine IMO. For functions, I agree with you that desugaring it into a destructured local would work well. For comprehensions, I think we should add the local to the comprehensions syntax as you proposed and as requested in #90. That can all wait though, the only important thing is that we're not painting ourselves into a corner (which we're not). So in summary your work so far looks quite good and I wouldn't throw it away. |
import * from "x"
Renamed the issue, because while the original proposal was rejected, the follow-up is pretty good. |
I'm not entirely sure if it fits under the original proposal, but there seems to be a 'feature hole' for destructuring objects within other objects. In python: > x = {"a": 1, "b": 2}
> {"c": 3, **x}
{'c': 3, 'a': 1, 'b': 2} This is useful if using top-level functions for your definitions. Say I'd like to create a TLA that dynamically composes other objects: local clients = import 'compose/clients.libsonnet';
local servers = import 'compose/servers.libsonnet';
// generate a config for a given client / server config
function(client, server) {
local clientFn = clients[client.name],
local serverFn = servers[server.name],
local clientCompose = clientFn(client.version, client.config),
local serverCompose = serverFn(server.version, server.config),
local dependency = {services+: {client+: {depends_on+: ["frontend"]}}},
**(clientCompose + serverCompose + dependency)
} The alternative (some dictionary expression) doesn't work. function(client, server) {
...
local compose = clientCompose + serverCompose + dependency,
[key]: compose[key] for key in std.objectFields(compose)
}
|
@arlyon This is not really related to this feature. This issue is about a more convenient way of defining |
I have experimented with this feature in Rust jsonnet implementation: https://github.com/CertainLach/jrsonnet/releases/tag/v0.5.0-pre2-test For now i implemented some basic syntax, i.e: Destructuring object local {a: b} = obj; ...
// Same as
local b = obj.a; ... Field name may be omitted: local {a} = obj; ...
// Same as
local a = obj.a; ... However, field name omission looks off here, as currently jsonnet doesn't allows local a = 1; {a}
// As sugar for
local a = 1; {a: a} Thus causing asymmetry Rest of fields may be collected into another object: local {a, ...rest} = obj; ... Destructuring arrays: local [a, b, c] = array; ... Rest of fields in any position may be collected into other array: local [...rest, a] = array; ...
local [a, ...rest] = array; ...
local [a, ...rest, b] = array; ... In case of not needed fields i propose local [?, b, c] = ["a", "b", "c"]; ... Contrary to syntax proposed here: #307 (comment) My implementation also allows recursive destructuring: local {a: [{b: {c: d}}]} = {a:[{b:{c:5}}]}; d == 5 Also mutually recursive declaration works: local
{a, b, c} = {a: y, b: c, c: x},
{x, y, z} = {x: a, y: 2, z: b};
z == 2 Things i have not implemeted yet is function parameters syntax, as this feature doesn't work with named arguments (It shouldn't, and destructured arguments shouldn't be passed by name?), and default assignment, as i thinking about proposal of pattern-matching on top of destructuring, and not sure how those features may look together local {
// Expected value, error will be thrown if `kind` is not equals to `Pod`
kind is 'Pod',
// Default value
a = 1,
} = obj; ...
// Then in future pattern matching may look like
match v
{kind is 'Pod', name} 'Pod named %s' % name,
{kind is 'Namespace', name} 'Namespace named %s' % name,
{kind = 'Unknown object', name = 'unnamed', ...} '%s named %s' % [kind, name],
other 'Not an object: ' + other; But how this should work with lazyness? local {a, b, c} = "string"; 2 + 2 Doesn't fail, because variables |
Destructuring will also provide general way for features like this: Not sure about golang/c++ impl, but in Rust i may implement this by making objects iterable (yielding [K, V] pairs), and then supporting destructuring inside object comprehensions: {[k]: v for [k, v] in object} Or like that: {[key]: value for {key, value} in object} |
Latest version of jrsonnet also implements destructuring in function arguments: local destruct({a}) = a;
destruct({a: 1}) == 1 And object destructuring defaults local {
a = b,
b = c,
c = d,
d = 6
} = {};
a == 6 While using destructuring in my own project I found I often write recursive declarations, and it is very useful to have infinite recursion detection for this case (jrsonnet does):
|
I didn't really jrsonnet was extending the language, have they got a list of features they've added? |
Out of interest, why does it use |
I only added two features so far, deconstruction (described in this thread) and object field ordering (described here: #903). Those features is not built by default
I.e local { a = b, d: e, f } = c;
// is a syntax sugar for
local
// if field `a` doesn't exists in `c`, then default to `b`
a = std.get(c, 'a', b),
// get field `d`, but store it in variable `e` (same syntax as in js)
e = c.d,
// get field `f`, and store it as `f`, `f` in deconstruction is a syntax sugar for `f: f`
f = c.f; |
Thanks for clarification. Did you implement it with desugaring? |
No, jrsonnet has no desugaring phase, it parses input to AST, and then evaluates it as-is local a = non_existing_variable;
true And instead of using ids for locals, jrsonnet interns every string/identifier, and performs O(1) comparsions on them. It is less clever than C++/golang implementation in every aspect :D Code for destructuring implementation is here: https://github.com/CertainLach/jrsonnet/blob/gcmodule/crates/jrsonnet-evaluator/src/evaluate/destructure.rs |
The desugaring is supposed to make it easier by reducing the number of constructs you have to handle in the backend and in static analysis. Unfortuantely it doesn't reduce the number of constructs you have to handle in tooling (e.g. reformatter) and we don't do that much static analysis so the benefit is somewhat muted :) |
👋 has there been any further consideration of this feature request? |
We use Jsonnet to express the Kubernetes API objects. The namespace is quite dense, so we end up with a lot of imports, something like [EDITED FOR CLARITY]:
It would be great to have sugar for this, e.g., JavaScript-style
import * from "core.libsonnet"
, which would generatelocal
definitions similar to those above.Would doing this upend some important part of the language?
The text was updated successfully, but these errors were encountered: