cue icon indicating copy to clipboard operation
cue copied to clipboard

comprehension: short-circuit support for if comprehension

Open FogDong opened this issue 2 years ago • 10 comments

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.

FogDong avatar Jan 31 '23 02:01 FogDong

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.

myitcv avatar Feb 01 '23 16:02 myitcv

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 && notation, and not and, for instance. But either way.

myitcv avatar Feb 01 '23 16:02 myitcv

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.

FogDong avatar Feb 02 '23 02:02 FogDong

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.

FogDong avatar Feb 09 '23 01:02 FogDong

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).

Somefive avatar Feb 10 '23 03:02 Somefive

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."

mpvl avatar Jun 16 '23 11:06 mpvl

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
}

seh avatar Jun 16 '23 18:06 seh

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.

I have a hard time imagining how anyone would rely on conditionals not short-circuiting. Could you please explain.

loewenstein avatar Sep 01 '23 15:09 loewenstein

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 && q is "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
    }
]

rogpeppe avatar Sep 01 '23 16:09 rogpeppe

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: []
	}
},

AntoineThebaud avatar Apr 17 '25 11:04 AntoineThebaud