Skip to content
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

Access to super in object comprehension #302

Closed
hausdorff opened this issue Feb 27, 2017 · 8 comments
Closed

Access to super in object comprehension #302

hausdorff opened this issue Feb 27, 2017 · 8 comments

Comments

@hausdorff
Copy link

hausdorff commented Feb 27, 2017

Currently if we write something like this:

{
  svc:: { a: "123" },
} + {
  [x]: "q" for x in std.objectFields(super.svc)
}

the compiler will complain that we Can't use super outside of an object. Similarly, if we do something like:

{
  svc:: { a: "123" },
} + {
  local s = super.deployment,
  [x]: "q" for x in std.objectFields(s)
}

the compiler will complain that Unknown variable: s.

I can see that there might be reasons why this might be the desired, but it seems to me that the + syntax is confusing in this case. When I use the + above, I would expect the comprehension to have access to super, but it seems based on the compiler error that we only have access to super after expansion. To me it seems like a better option would be to (1) simply give the subclass access to super, or (2) find a way in the syntax to indicate more clearly that the comprehension is not closed over the containing object.

EDIT: A third option would be to state the error clearer.

@sparkprime
Copy link
Contributor

You can't use self in the ... below:

{
    [...]: e
    for x in ...
}

because when that code is being executed, the object isn't actually built yet. So if you could use self, you can quite easily create things that are circular in meaning:

{ [std.objectFields(self)[0]]: 1 }

or

{ [self.f]: 'f' }

So you can't use super for the same reason you can't use self. You can't access in-object locals because that could give indirect access to self/super.

Several people have reported this as not intuitive, though. You are probably right that the documentation / error message should be improved. I suppose the problem is that intuitively people think "inside the object" means between the { and }, which is true except for this edge case.

@sparkprime
Copy link
Contributor

@hausdorff
Copy link
Author

Ah, yes, completely makes sense. It's not the highest priority for me, but I am hoping to find some time in the next couple weeks to come back and contribute a patch that makes the error something clearer.

Narrowing the focus of the above example for a bit, what we're actually trying to do is take an object and "expand" it (or "splat" it, cf.) it into the current object's namespace, i.e. take all the keys of super.svc and place them in self. What is the "right way" to solve this problem? You could do something like:

local foo = {
  svc:: { a: "123" },
}.svc

but with this, you lose the equational reasoning of +. For example, if you have something like a + foo and you attempt to reference something in a by calling super in foo, then it will fail to resolve super, because what is "returned" from the expression is actually svc.

If there was something like a splat operator, then I think many of the cases where you would want to do something like the above would no longer be a problem, as you could concisely express it with something like ...super.svc.

@hausdorff
Copy link
Author

hausdorff commented Feb 27, 2017

Another possibility -- and I should say that I'm not sure what comes unhinged if you start messing around with this -- is to reconsider when the . operator binds. If it binds left-associative, but is looser than + (i.e., so that a + b.c + d == (a+b).c + d) then it seems like it could be possible to maintain the same equational reasoning as above.

As I said, I'm not sure what impact this has on the operational semantics of the language, but it seems to me that there are at least some cases where this evaluation would be more intuitive.

@sparkprime
Copy link
Contributor

Sorry I didn't follow your explanation of what you want to do. Why isn't it covered by the basic inheritance idiom?

local to_expand = {
  svc: { a: 1, b: self.a },
};
local expand_into = to_expand.svc + {
  a: 2,
  x: "foo %s bar %s" % [self.a, self.b],
};

yields

{
   "a": 2,
   "b": 2,
   "x": "foo 2 bar 2"
}

@hausdorff
Copy link
Author

Sorry, let me give you a more concrete situation and then ask your advice.

The exact situation (which I suspect is highly un-idiomatic) is that we want to "return" something from a function, something like:

local foo() = {
  local b = bar(super.a),
  impl: baz(b),
};

{ a: "baz" } + foo().impl

This doesn't work. Jsonnet will complain that there is no super, because impl is what is inheriting from { a: "baz" }. In other words { a: "baz" } + foo().impl != ({ a: "baz" } + foo()).impl.

So, what do you think we should do in this situation? Am I just doing something that's hopelessly un-idiomatic? It sounds like your advice might be to just write it a different way.

@sparkprime
Copy link
Contributor

sparkprime commented Mar 4, 2017 via email

@hausdorff
Copy link
Author

Ok, based on this reply, I understand you to be saying that changing the binding/precedence is not on the table, and that the "Jsonnet-y way" of doing this is to just do what I had considered earlier, namely ({ a: "baz" } + foo()).impl. :)

Is that correct? I'll close the issue and if I have misunderstood you, let's open it and discuss further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants