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

If Without Else #234

Closed
rhdunn opened this issue Nov 8, 2022 · 11 comments
Closed

If Without Else #234

rhdunn opened this issue Nov 8, 2022 · 11 comments
Labels
Enhancement A change or improvement to an existing feature XPath An issue related to XPath

Comments

@rhdunn
Copy link
Contributor

rhdunn commented Nov 8, 2022

This is based on the discussions in today's QT4CG meeting, as suggested by @dnovatchev.

Use Case

It is common to have an if condition where the else branch does nothing (i.e. yields the empty sequence). In BaseX, this requirement has lead to them making the else branch optional.

It can also be conceivable to want to elide the then branch as a way of not negating the if condition expression.

In the XSLT 4.0 draft, both of these are possible in the changes to support optional @then (or possibly @select) and @else attributes. Part of this is due to requiring backward compatibility with XSLT 3.0 that only allows the then branch within child elements. It would be nice to be able to support this in XPath and XQuery for parity between the languages.

Design

The rationale for not allowing an optional else is to avoid the dangling else problem from C and other languages that have optional else statements. That is:

if ($c) then
    if ($d)
    then 1
    else 2

is ambiguous as the else could be part of the $d if statement or the $c if statement. This would require parenthesis around the if statement to resolve the ambiguity, but the syntax should not require that. -- That is, it should be clear to the reader what the if statement will do.

As such, I propose the following variants:

(1) An if statement with a then and else expression -- currently supported:

if ($c) then 1 else 2

(2) An if statement with an else expression, but not a then expression -- new, should be unambiguous:

if ($c) else 2

(3) An if statement with a then expression, but not an else expression -- to resolve the dangling else, I propose to use return instead of then:

if ($c) return 1

This works analogously to the existing FLWOR, switch, typeswitch and other expressions that use return to denote the return/result expression.

For the dangling else, both cases are clear:

(a) when the $c (outer) if has the elided else expression:

if ($c) return
    if ($d)
    then 1
    else 2

(b) when the $d (inner) if has the elided else expression:

if ($c)
then if ($d) return 1
else 2

Syntax

Replace:

IfExpr ::= "if"  "("  Expr  ")"  "then"  ExprSingle  "else"  ExprSingle

with:

IfExpr ::= IfClause (( ThenClause? ElseCause ) | ReturnClause)
IfClause ::= "if"  "("  Expr  ")"
ThenClause ::= "then"  ExprSingle
ElseClause ::= "else"  ExprSingle

Design note: There is a ReturnClause symbol that is defined as "return" ExprSingle. Because of this, and to indicate that the then and else parts are not expressions, I've opted for the term clause. -- This matches the use in FLWORExpr, SwitchExpr, TypeswitchExpr, etc. that all use the term clause. I've also separated them out to make the IfExpr more readable now that it has optional parts.

Semantics

Replace:

The expression following the if keyword is called the test expression, and the expressions following the then and else keywords are called the then-expression and else-expression, respectively.

with:

The expression between the parenthesis after the if keyword is called the test expression.

The expression after the then or return keyword is called the then-expression. If this is missing, it defaults to the empty sequence.

The expression after the else keyword is called the else-expression. If this is missing, it defaults to the empty sequence.

Those should be the only required changes.

@rhdunn rhdunn added XPath An issue related to XPath Enhancement A change or improvement to an existing feature labels Nov 8, 2022
@ChristianGruen
Copy link
Contributor

I like the proposal! It’s both intuitive and unambiguous. I wonder if we should support else without then, though. It’s less frequent and may not be self-explanatory (?).

@rhdunn
Copy link
Contributor Author

rhdunn commented Nov 8, 2022

I included the else without then for parity with XSLT. I'm happy leaving that bit out if the consensus is that it is not needed.

@dnovatchev
Copy link
Contributor

Good proposal, @rhdunn !

Just this: I also think that else without then is kinda unnatural and confusing. One can always use if(not($c)) then Expr

@liamquin
Copy link

liamquin commented Nov 9, 2022

good ideas here. Agree else by itself prolly bad - more likely to be a mistake than intended.

As a side not, an ifless then or else should remain an error for the same reason - to catch mistakes.

@michaelhkay
Copy link
Contributor

My first instinct was that this is imaginative, and I quite like it.

But I'm concerned about this:

declare function ($e) {
   if (exists($e/@one)) return $e/@one,
   if (exists($e/@two)) return $e/@two
}

because its meaning is not at all what a C or Java programmer would expect: I think this would become a fruitful source of bugs.

As an alternative, how about abandoning the ternary conditional proposed in issue #171, and using

declare function ($e) {
   exists($e/@one) ?? $e/@one,
   exists($e/@two) ?? $e/@two
}

@michaelhkay
Copy link
Contributor

Another possibility would be

declare function ($e) {
   if (exists($e/@one)) { $e/@one },
   if (exists($e/@two)) { $e/@two }
}

@ChristianGruen
Copy link
Contributor

because its meaning is not at all what a C or Java programmer would expect: I think this would become a fruitful source of bugs.

I assume that’s similar with our existing syntax:

if (exists($e/@one)) return $e/@one,
if (exists($e/@two)) return $e/@two

Indeed, newcomers will never understand why return was chosen as a final keyword, as it is entirely different from procedural languages. On the other hand, it’s something you can quickly get used to.

What about enforcing the first parenthesis if no else is used? Something like…

IfExpr ::= IfClause (
  "(" ThenClause ")" ElseCause? |
  ThenClause ElseCause
)

Examples:

(: legal :)
if(A) then B else C
if(A) then (B)
if(A) then (if(B) then (C))

(: illegal :)
if(A) then B
if(A) then if(B) then (C)

In general, I’m still wondering why the dangling else problem is really such a showstopper, as there are various other implicit rules that developers need to understand. For example, users need to know about precedence rules to understand how 1 + 2 * 3 will be evaluated. In our case, users simply must know that an else branch refers to the innermost if expression.

Developers have become used to this in so many other languages. Why do we expect them to be overwhelmed when we introduce it in our language?

@michaelhkay
Copy link
Contributor

michaelhkay commented Nov 9, 2022

Regarding "dangling else", I seem to recall the WG consensus was "just because other programming languages made this mistake doesn't mean that we need to make it too". (Getting the if-then-else syntax agreed was one of Mary Fernandez' major triumphs, by the way. The arguments were very heated. Parking the dangling else problem, leaving that fight to another day, was the best way of resolving the issue at the time.)

I don't think the problem is that users will be overwhelmed. The problem is that they will make mistakes through carelessness, as they do in other languages. Personally, I avoid the problem in Java by always using curly braces. I think it's better language design if good coding practice is mandatory wherever possible.

The requirement to use parens feels rather clumsy to me. It seems to break an orthogonality principle, though I find it hard to identify exactly why it makes me nervous. I prefer my curly-braces proposal.

@ChristianGruen
Copy link
Contributor

Thanks for the insight. As often, it feels obstructive and liberating at the same time not to be loaded with the historical context.

I like the curly braces, and the syntax is compact and well-known. With my latest proposal, I agree it would be tiresome to explain why the additional ( is required, and why it can be omitted if we have an else branch.

@benibela
Copy link

benibela commented Nov 9, 2022 via email

@michaelhkay
Copy link
Contributor

This has been resolved. We now have if (condition) {something} defaulting to else {()}.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement A change or improvement to an existing feature XPath An issue related to XPath
Projects
None yet
Development

No branches or pull requests

6 participants