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

Feature request: making "else" optional #1824

Closed
chancez opened this issue Feb 15, 2019 · 23 comments
Closed

Feature request: making "else" optional #1824

chancez opened this issue Feb 15, 2019 · 23 comments

Comments

@chancez
Copy link
Contributor

chancez commented Feb 15, 2019

I think it would really help shorten expressions if the else clause inside an if could be omitted, returning . by default.

For example the following two programs would be considered equivalent:

if .foo then
    .foo = "bar"
else
    .
end
if .foo then
    .foo = "bar"
end

@chancez
Copy link
Contributor Author

chancez commented Feb 15, 2019

I just wrote a patch for this, and it seemed pretty easy. If this seems reasonable I'm glad to make a PR. The main thing is I'm noticing that my newer bison updates a lot of stuff unnecessarily in the parser files.

@nicowilliams
Copy link
Contributor

The thought has occurred to me before. My concern had been whether the else . default would be obvious (as opposed to... else empty). If others want this though, that may be the evidence that it's obvious enough!

Maybe tomorrow I'll have time for jq code reviews...

@chancez
Copy link
Contributor Author

chancez commented Feb 15, 2019

I tried to consider that as an option too, but it didn't make a ton of sense when I compare to other languages. I think empty would be odd since in most languages an if statement without an else that evaluate to false, is a no-op, which is what . does.

@nicowilliams
Copy link
Contributor

We already have select() for them else empty case, so there was no question of making the else default to empty :)

@pkoppstein
Copy link
Contributor

@chancez - There has never been much doubt that if if COND then X end were to be supported, it would have the semantics of if COND then X else . end; the question has been whether people might expect if empty then X end to be equivalent to . or to empty.

In my mind, therefore, the question has been whether to support if without else on the one hand, or on the other to support:

def when(cond; X): if cond // null then X else . end;

That is, if this (or a similar) def of when were added, the impetus for supporting if without else would be greatly reduced, and the potential for confusion about its semantics would be irrelevant.

@chancez
Copy link
Contributor Author

chancez commented Feb 15, 2019

Oh i see.

While I think a when function could make sense, that doesn't seem to really change the usefulness of else-less if conditions.

when to me, seems like a further improvement that could probably considered independently, and it could even take advantage of else-less if conditions, which to me indicates both features would be useful independently.

@chancez
Copy link
Contributor Author

chancez commented Feb 15, 2019

Further, it seems this doesn't quite make sense to me:

the question has been whether people might expect if empty then X end to be equivalent to . or to empty.

This sounds a lot more like the following condition expression: empty | if . then X else . end which isn't at all the same as if COND then X else . end. In the one case, the condition is the same as the input expression, the other, condition is a separate expression.

That said, I'm having a hard time thinking about this so I might be missing the issue.

@pkoppstein
Copy link
Contributor

@chancez: Yes, you might be missing the issue. Consider expressions like these:

 if empty then 1 else 2 end

 if empty then 1 else 2,3 end

@chancez
Copy link
Contributor Author

chancez commented Feb 15, 2019

I definitely think I am missing something, but I'm not sure what your trying to illustrate.

@chancez
Copy link
Contributor Author

chancez commented Feb 15, 2019

I'm trying out when in my jq program, and I'm finding it to be less than ideal syntactically. It definitely makes the conditionals less readable. It seems useful, but not in all cases.

For example, between these 3, I think the else-less if conditional is the easiest to read:

If else:

| if notEmpty(namedArg("repository")) and notEmpty(namedArg("tag")) then
    # Update the image for each container in the pod
    .spec.template.spec.containers[].image = (namedArg("repository") + ":" + namedArg("tag"))
else
    .
end

| if notEmpty(namedArg("allNamespaces")) then
    # Update the env var ALL_NAMESPACES
     updateDeploymentEnv(.; "ALL_NAMESPACES"; namedArg("allNamespaces"))
else
    .
end
| if notEmpty(namedArg("targetNamespaces")) then
    # Update the env var TARGET_NAMESPACES
    updateDeploymentEnv(.; "TARGET_NAMESPACES"; namedArg("targetNamespaces"))
else
    .
end

using when(cond; X):

| when(notEmpty(namedArg("repository")) and notEmpty(namedArg("tag"));
    # Update the image for each container in the pod
    .spec.template.spec.containers[].image = (namedArg("repository") + ":" + namedArg("tag"))
)

| when(notEmpty(namedArg("allNamespaces"));
    # Update the env var ALL_NAMESPACES
     updateDeploymentEnv(.; "ALL_NAMESPACES"; namedArg("allNamespaces"))
)

| when(notEmpty(namedArg("targetNamespaces"));
    # Update the env var TARGET_NAMESPACES
    updateDeploymentEnv(.; "TARGET_NAMESPACES"; namedArg("targetNamespaces"))
)

Using if without else:

| if notEmpty(namedArg("repository")) and notEmpty(namedArg("tag")) then
    # Update the image for each container in the pod
    .spec.template.spec.containers[].image = (namedArg("repository") + ":" + namedArg("tag"))
end

| if notEmpty(namedArg("allNamespaces")) then
    # Update the env var ALL_NAMESPACES
     updateDeploymentEnv(.; "ALL_NAMESPACES"; namedArg("allNamespaces"))
end

| if notEmpty(namedArg("targetNamespaces")) then
    # Update the env var TARGET_NAMESPACES
    updateDeploymentEnv(.; "TARGET_NAMESPACES"; namedArg("targetNamespaces"))
end

@pkoppstein
Copy link
Contributor

@chancez wrote:

I'm not sure what your trying to illustrate.

The crux of the matter is that some people might reasonably expect:

0 | if empty then 1 end 

to emit 0, while others might reasonably expect it to emit nothing.

(Perhaps you might try to consider what you would have expected had you not already implemented it.)

Please note that I'm quite comfortable about jq supporting if without else but would hope that if that transpires, the documentation will be abundantly clear about its semantics.

@chancez
Copy link
Contributor Author

chancez commented Feb 15, 2019

I see. Yeah, I can see what you mean now. I guess the way I see it is how other languages behave. Never have I see a failing if condition (without another branch like an else) change the control flow of something, so I'd never expect empty output from that program. I could see how others might see it differently though.

It could certainly be argued if you "optimized away" the condition, you would be left with something like 0 | ...... where .... is either . or empty depending on how you think about it. Definitely not as obvious as I immediately thought.

@nicowilliams
Copy link
Contributor

Note that if empty then ... == empty anyways.

@chancez
Copy link
Contributor Author

chancez commented Feb 15, 2019

Oh, I see. That's....interesting.

@chancez
Copy link
Contributor Author

chancez commented Feb 15, 2019

Is that because it's neither true or false?

@wtlangford
Copy link
Contributor

Is that because it's neither true or false?

It's because empty triggers a backtrack, where we unwind the stack until we reach a fork point (anything that can output more than one value. .[] is the easiest example).

[1,2,3] | .[] | if . == 2 
  then
    (if empty then "EMPTY" else . end)
  else
    .
  end

This outputs

1
3

@chancez
Copy link
Contributor Author

chancez commented Feb 16, 2019

Ah, empty is a way more nuanced and tricky than I understood it to be. Thanks.

@nicowilliams
Copy link
Contributor

@pkoppstein I think if empty then ... is completely orthogonal to whether the else clause can default to . (or something else). What am I missing?

@nicowilliams
Copy link
Contributor

@chancez Regarding empty, perhaps this wiki page might help: https://github.com/stedolan/jq/wiki/jq-Language-Description

@pkoppstein
Copy link
Contributor

@nicowilliams - It's odd that you think you might be missing something, because you yourself wrote:

My concern had been whether the else . default would be obvious

In any case, it seems to me that the main argument against supporting if without else is that SOME users (indeed, I believe @chancez might have been one of them) MIGHT expect

if empty then X end

to be identical to:

if empty // false then X else . end

rather than to

if empty then X else . end

(I've emphasized this potential confusion rather then the third possible interpretation because, as you said, the potential reading of if A then X end as if A then X else empty end is somewhat implausible in that select effectively provides that functionality.)

@nicowilliams
Copy link
Contributor

@pkoppstein To me the form of the conditional has nothing to do with anything here. I have before worried that the default else would not be obvious, but with select/1 defined as it is, I think it's pretty clear that . is the only reasonable default for else: if you wanted empty, then you should use select/1.

Maybe we should have a cond/2 builtin instead:

def cond(c; t): if c then t else . end;

But that doesn't help if you have elifs...

@pkoppstein
Copy link
Contributor

pkoppstein commented Feb 17, 2019

@nicowilliams - It's genuinely puzzling to me that when I explicitly agree with you, you seem to see disagreement.

By the way, now that the main obstacle to adding to builtin.jq has been removed, could you please consider #1260 -- it's been over two years since Erik made this important contribution.

@nicowilliams
Copy link
Contributor

Yes, thanks for the reminder, we;ll restart integration of new builtins. We should, however, have a concept of builtin modules that one could import:

$ jq --no-builtins ... '
# Only byte-coded and C-coded builtins
# We can import/include jq-coded builtins:
include "jq/jq";      # basic builtins
include "jq/math";
include "jq/...";
...

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

4 participants