comprehension: short-circuit support for if comprehension
Is your feature request related to a problem? Please describe. Currently there is no short-cut support for if comprehension, so a cue value like this will report an error:
a: {...}
if a.b != _|_ && a.b == true {
b: true
}
Cue playground link
Describe the solution you'd like
Support short-circuit in if comprehension.
Just to be clear, @FogDong, when you say this reports an error I'm assuming you mean via cue export?
! exec cue export x.cue
cmp stderr stderr.golden
-- x.cue --
a: {...}
if a.b != _|_ && a.b == true {
c: true
}
-- stderr.golden --
undefined field: b:
./x.cue:3:20
I'm actually not clear that this isn't a bug (apologies for the double negative).
cc @mpvl
Note: this is not a regression vs v0.4.3. Same behaviour there.
I'm being a bit slow as I ease back into work this year :)
You're quite right, @FogDong - this is working as expected right now, so you were also quite right to flag this as a feature request. My apologies.
Note that in the interim you can instead write:
exec cue export x.cue
cmp stdout stdout.golden
-- x.cue --
a: {...}
if a.b != _|_ if a.b == true {
b: true
}
-- stdout.golden --
{
"a": {}
}
See also https://github.com/cue-lang/cue/issues/313.
Per @mpvl offline:
There has been a request for it, but too many people have said they prefer as is. I'm personally quite ambivalent about the semantics.
I would personally be okay with C semantics, especially since we use the
&¬ation, and notand, for instance. But either way.
Hi @myitcv , thanks for the reply!
if a.b != _|_ if a.b == true {
b: true
}
This looks like a fair way to avoid the error and I can use it as a workaround.
This one's priority is high.
Though there is a workaround, most of our users do not know this workaround and double if might look strange to our users.
This is a user facing issue because in our workflow, users can write an if condition(which will be evaluated as cue value) to decide whether a step should be executed. Users can also pass data from other steps as inputs, so the step might look like:
steps:
...
- name: my-step
inputs:
- from: resource
if: inputs.resource.status != _|_ && inputs.resource.status.phase == "ready"
type: apply-object
properties: ...
If there's no short-circuit, the inputs.resource.status != _|_ && inputs.resource.status.phase == "ready" will report an error. And most of our users are using workflow in yaml, so double if is foreign to them.
No time constraint, but it would be better if this can be released in v0.6.
Compared to short-circuilt, I also wonder if it could be possible in the future that CUE could support optional chaining, such as a.b?.c?.d like JavaScript, TypeScript. I think the short-circuit usually works for safe-guarding the logic behind in avoid of null pointer exception in many languages. But the virtual logic that users want to do is to check the following logic after the null check (or bottom check in CUE).
There are various thoughts on whether && should follow C semantics. The general intuition of the CUE users we asked is that the current behavior is in the spirit of how CUE is. In that regard it is a bit unfortunate that we used && as an annotation. Given the meaning of && in other languages, using and might have been intuitively closer to this interpretation.
That said, the particular example/ use case is a very common. We noticed that the query proposal can be of help here. We have concluded that there is no way around having a new operator for streaming queries. Let's say ?? (other ideas were query or <-, but let's avoid bikeshedding here.) With such an operator, the example could be written as:
b: ?? a.b
which is interpreted as "if a.b evaluates to a value, add a field b with that value."
If a.b. does not evaluate to a value, do we wind up with field "b" present with pseudo-value _|_ (bottom), or does this forestall defining field "b" at all? That is, would one still need to write the following to avoid defining "b"?
if a.b != _|_ {
b: a.b
}
There are various thoughts on whether
&&should follow C semantics. The general intuition of the CUE users we asked is that the current behavior is in the spirit of how CUE is. In that regard it is a bit unfortunate that we used&&as an annotation. Given the meaning of&&in other languages, usingandmight have been intuitively closer to this interpretation.
I have a hard time imagining how anyone would rely on conditionals not short-circuiting. Could you please explain.
I don't think it has been mentioned in this issue before, but it should have been: the current wording of the spec supports short-circuiting logical operators AFAICS:
The right operand is evaluated conditionally.
and also:
p && qis "if p then q else false"
In the context of CUE, the exact semantics of that if ... then ... else expression aren't totally clear, but given that the following expression is valid in current CUE, I think it's reasonable to assume that the intended semantics were to short-circuit the logical operators.
x: y?: string
z: [
if x.y != _|_ {
x.y
}
]
In my case short-circuit would help avoiding code duplication where I would like to be able to use a || clause.
I have several pieces of CUE code that looks like the following:
// convert object from tool A to tool B if applicable
if #obj.type == "query" if (#obj.query & string) != _|_ if #obj.query =~ "^thingToMatch$" {
kind: "MyMigratedObject"
spec: {
matchers: []
}
},
if #obj.type == "query" if (#obj.query & {}) != _|_ if #obj.query.query =~ "^thingToMatch$" {
kind: "MyMigratedObject"
spec: {
matchers: []
}
},
With short-circuit I would be able to do something like:
// convert object from tool A to tool B if applicable
if #obj.type == "query" && (
((#obj.query & string) != _|_ && #obj.query =~ "^thingToMatch$") ||
((#obj.query & {}) != _|_ && #obj.query.query =~ "^thingToMatch$")) {
kind: "MyMigratedObject"
spec: {
matchers: []
}
},