Xquery: same test has different result whether used in switch/case or in if/then/else
Asked Answered
D

1

7

I can't find an explanation on the following. I've made this test script in order to solve my previous question.

xquery version "3.0" ;
declare default function namespace 'local' ;

declare function local:is-img-only( $element as element() )  as xs:boolean {    
   ($element/child::*[1] instance of element(img)) 
   and (fn:not($element/child::*[2])) 
   and (fn:normalize-space($element) = '')
} ;

let $in-xml := <myxml>
   <p id="1">

      <img id="1"/>

   </p>
   <p id="2">
      <img id="1"/>
      hello
   </p>
   <p id="3">
      <img id="1"/>
   </p>
   <p id="4">
    <blockquote>hello</blockquote>
      <img id="1"/>
   </p>
   <p id="5">
      <img id="1"/>
      <img id="2"/>
   </p>
</myxml>

Then, the following using if then else :

for $p in $in-xml/p
  return if (local:is-img-only($p)) 
    then $p/@id/fn:data() || ' has only an img child'
    else $p/@id/fn:data() || ' has not strictly an img child'

returns as expected :

1 has only an img child
2 has not strictly an img child
3 has only an img child
4 has not strictly an img child
5 has not strictly an img child  

Whereas the following using switch case

for $p in $in-xml/p
  return switch ($p)
    case (local:is-img-only($p)) return $p/@id/fn:data() || ' has only an img child'
    default return $p/@id/fn:data() || ' has not strictly an img child'

returns as not expected :

1 has not strictly an img child
2 has not strictly an img child
3 has not strictly an img child
4 has not strictly an img child
5 has not strictly an img child

Any explanation ? Why Conditional Expressions would not behave the same way than Switch Expressions ?

Denominative answered 11/1, 2018 at 2:40 Comment(0)
S
4

Great job in locating the relevant sections of the XQuery specification for your question! That's half the battle. Switch expressions do share some things in common with conditional expressions, but there are some differences.

The key difference is that a conditional expression evaluates a test expression to see if the result is true or false, while a switch expression compares one expression against one or more other expressions to find the first pair that are equal. Using pseudo code, we can illustrate the difference. All of the logic in a conditional expression takes place here on one line:

if ($EXPRESSION)

In contrast, the logic of a switch expression is spread over many lines:

switch ($EXPRESSION_A)
    case ($EXPRESSION_B) ...
    case ($EXPRESSION_C) ...
    case ($EXPRESSION_D) ...
    default ...

The switch expression is actually performing a chain of comparisons, which we could express as even more lines of conditional expressions:

if (deep-equal($EXPRESSION_A, $EXPRESSION_B))
then ...
else 
    if (deep-equal($EXPRESSION_A, $EXPRESSION_C))
    then ...
    else ...
        if (deep-equal($EXPRESSION_A, $EXPRESSION_D))
        then ...
        else ... (: "default" :)

The nitty gritty differences between these two expressions are described in the spec, starting with where they describe "the first step" in processing each expression. Whereas the first step in processing a conditional expression is:

to find the effective boolean value of the test expression.

... the first step in processing a switch expression is:

to apply atomization to the value of the switch operand expression.

Let's return to your concrete example and take a look at your conditional's test expression and your switch's switch operand expression:

  1. Your conditional's test expression:

    if (local:is-img-only($p)) 
    
  2. Your switch's operand expression:

    switch ($p)
    

The conditional's test expression returns a boolean - true() or false(), so this condition clearly charts out the path of the rest of the code.

In contrast, the logic of the switch expression has only just begun with this operand expression. First, it finds the atomized value of the operand expression—the <p> element bound to the $p variable in your FLWOR expression. Since this value depends on which <p> we're looking at, the atomized value will either be an empty string (""), whitespace, or "hello" (or some combination thereof, depending on whitespace in your source and your boundary space declaration). Then, the switch's first case operand is evaluated and atomized. Your first case operand is as follows:

case (local:is-img-only($p))

This expression, as we'll recall, evaluates to a boolean. The next step the switch expression performs is to compare the atomized value of the switch operand expression with the atomized value of the switch case operand using the fn:deep-equal function. Effectively, then, we're asking the XQuery processor to perform the following comparisons:

deep-equal("", true())
deep-equal("hello", false())

In both cases the comparison returns false(). Thus, this comparison in our case operand always fails, so the switch expression is falling back on the default clause in every iteration of your FLWOR expression.

A switch expression that mimics the results of your original conditional expression would be the following:

for $p in $in-xml/p
  return switch(local:is-img-only($p))
    case (true()) return $p/@id/fn:data() || ' has only an img child'
    default return $p/@id/fn:data() || ' has not strictly an img child'

This performs the following checks:

deep-equal(true(), true())
deep-equal(true(), false())

And returns the identical results as your conditional expression.

This isn't a particularly compelling use for the switch expression - since we're effectively evaluating a single test expression. The switch expression really shines when you have many values to compare. The spec offers us a good example switch expression to consider:

switch ($animal)
    case "Cow" return "Moo"
    case "Cat" return "Meow"
    case "Duck" return "Quack"
    default return "What's that odd noise?"

This is far more readable and compact than the equivalent conditional expression:

if (deep-equal($animal, "Cow")) 
then "Moo"
else 
    if (deep-equal($animal, "Cat")) 
    then "Meow"
    else
        if (deep-equal($animal, "Duck"))
        then "Quack"
        else "What's that odd noise?"

Or even the more straight-forward interpretation:

if ($animal eq "Cow")
then "Moo"
else 
    if ($animal eq "Cat")
    then "Meow"
    else
        if ($animal eq "Duck")
        then "Quack"
        else "What's that odd noise?"

The upshot: if you ever find yourself writing a chain of conditionals, and the left side of the comparison is always the same, consider making a switch.

Suzannesuzerain answered 11/1, 2018 at 4:38 Comment(3)
Great answer, very clear and thanks for quoting the specifications, which you made much more understandable. Indeed the use of switch case in that example is not relevant, I was only testing my expressions and making sure that if and switch had different behaviours. Now that you have confirmed it, how come <blockquote> passthru whereas <img/> doesn't in my previous question.Denominative
And I must link again the best xquery ressource since DH MTL, thanks to @joewizDenominative
I'm glad to hear that this helped and that you found that XQuery resource helpful! I'll put your question back to you. Look carefully at your switch operand expression ($node), and think through what its atomized value is; then look at each case operand expression (e.g., $node/spip:* instance of element(spip:img) and fn:not($node/text()[fn:normalize-space(.) != '']) and think through what the atomized value of each is is. What happens when the XQuery processor performs the deep-equal comparison on each?Suzannesuzerain

© 2022 - 2024 — McMap. All rights reserved.