me.at.coding
me.at.coding

Reputation: 17724

Updating variables in XQuery - possible or not?

I'm a little bit confused concerning variable updates in XQuery: On [1] it says:

Variables can't be updated. This means you can't write something like let $x := $x+1. This rule might seem very strange if you're expecting XQuery to behave in the same way as procedural languages such as JavaScript. But XQuery isn't that kind of language, it's a declarative language and works at a higher level. There are no rules about the order in which different expressions are executed (which means that the little yellow triangle that shows the current execution point in the Stylus Studio XQuery debugger and XSLT debugger can sometimes behave in surprising ways), and this means that constructs whose result would depend on order of execution (like variable assignment) are banned.

I'm wondering if there is really no way to reliable update a variable? Maybe I'm just to used to those things in other languages, but I can't really imagine / believe it ;-)

[1] http://www.stylusstudio.com/xquery_flwor.html, second paragraph below the screenshots of the chapter "L is for LET"

UPDATE: I have to add a question to this: Shouldn't it be possible to update an existing variable in an if statement because in this case the order of execution is clear? I guess you just aren't allowed to use something like $x = $x+1 in a loop?

Upvotes: 8

Views: 24196

Answers (5)

Dennis Münkle
Dennis Münkle

Reputation: 5071

Actually, updating a variable is possible if your XQuery processor supports the XQuery Scripting Extension 1.0

E.g., the following example works in the Zorba Sandbox:

declare namespace an = "http://zorba.io/annotations";

declare %an:sequential function local:fib(){
  variable $a as xs:integer := 0;
  variable $b as xs:integer := 1;  
  variable $c as xs:integer := $a + $b;
  variable $fibseq as xs:integer* := ($a, $b);
  while ($c < 100) { 
     $fibseq := ($fibseq, $c);
     $a := $b;
     $b := $c;
     $c := $a + $b; 
  } 
  $fibseq
};

local:fib()

A sequential function can do updates. The apply statement (each piece of code ending with ;) applies all updates immediately.

If you just want to have a counting variable in a FLWOR you can use the at keyword:

for $item at $x in ("a","b","c")
return $x

returns:

<?xml version="1.0" encoding="UTF-8"?>
1 2 3

Upvotes: 8

Cefn Hoile
Cefn Hoile

Reputation: 441

In answer to your first question about variables, and second question about if statements.

You can't change the value of a variable (a stupid name isn't it, given they don't vary!).

The magic pixie dust for using XQuery with any complex logic (and something like mutable variables) is recursion.

Robert Harvey mentioned the language construct of the for loop, in which the variable changes each time, which is highly relevant, but that can't always solve your problems unless your intended behaviour can be achieved by simple iteration of a list. What you were asking for was mutable variables.

With recursion your functions call themselves. This means they can pass in a modified value to the next function invocation in place of the value they already have. It more or less adds up to a mutable variable, but permits the benefits of a functional language to remain.

It can be a bit of a headfuzz to switch from a procedural (sequential execution of steps) way of thinking to a functional (simultaneous evaluation of clauses) way of thinking.

Recursion allows the clauses which are actually evaluated to depend on complex logic which kind of looks sequential. In practice you're simply creating clauses which demand others to be evaluated before they can evaluate themselves, which is not quite the same thing.

Here is a stupid, entirely irrelevant and totally untested example which shows how a list can be modified 'on the fly' as it is traversed by recursion, something which is impossible in a for loop.

Note the variable $patternsremaining is strictly a new variable (calculated partly based on $patterns). Whatever patterns are passed as an argument into the recursive call are assigned to $patterns within the new function invocation.

(: Here $patterns looks like <pattern match="something" replace="else" /> :)
declare function local:transform($text as text(), $patterns as element(pattern)*) {
   if(not($patterns)) then 
      $text
   else
      let $patternsremaining := $patterns[position() > 1],
          $modifiedtext := replace($text, $pattern/@match, $pattern/@replace)
      return 
         if($local:language="French" and not($patterns[@match='le'])) then (
             local:transform($modifiedtext, ($patternsremaining, <pattern match="Londres" replace="London" />))
      )
      else(
         local:transform($modifiedtext, $patternsremaining)
      )
};

For hardcore activities with XSLT and XQuery (e.g. writing compilers) recursion is the only model I've found with sufficient power. Real examples have a tendency of looking even more complicated than the one above, though.

As regards the if() then () else () statement, because it's feasible that the same execution context (if you're a procedural coder think 'combination of stack variables') was encountered before, and the same expression was evaluated already somewhere else, then the if statement will NEVER be evaluated again, because the interpreter can cache the result, based on the previous invocation. It's therefore not strictly true that you can rely on the sequence. It may not be run at all!

This is possible because it is built into the language definition that there can be no side effects which would change the result of a functional evaluation (hence those variables which don't vary).

This cacheability is a central feature of the functional approach. It creates the potential for highly efficient interpreters to be written, but it requires you to think recursively if you want to be able to operate with mutable values.

Upvotes: 4

abdollar
abdollar

Reputation: 3415

Believe it.

XQuery is like a functional programming language in this regard. You don't want to change the value of already calculated computation from a functional/mathematical perspective. Also, data is immutable and cannot change state so the language can be used to build high concurrency applications. Erlang is a "good" example of a language built for high concurrency. Immutability is also used in some imperative languages such as Java for high concurrency.

Just like in any other functional language you can always ensure you "eval" your "expr" and then make the required changes with a copy.

Note that I am not saying XQuery is a good functional programming language. Have a look at erlang for an example of a good functional language and implementation that provides good performance.

Upvotes: 0

Robert Harvey
Robert Harvey

Reputation: 180858

You are describing immutability, a feature of functional languages. It's true; once a variable is set to a value, it cannot be set to something else.

Immutability has many benefits. In particular, concurrent programming is made much easier.

In the case of loops, what happens is that a new variable is created each time through the loop, replacing the original one. So immutability still holds. This is explained in detail in the article you linked:

Isn't there a variable being updated when you write something like the following?

for $v in //video
let $x := xs:int($v/runtime) * xdt:dayTimeDuration("PT1M")
return concat($v/title, ": ", 
      hours-from-duration($x), " hour(s) ",
      minutes-from-duration($x), " minutes")

(This query shows the running time of each video. It first converts the stored value from a string to an integer, then multiplies it by one minute (PT1M) to get the running time as a duration, so that it can extract the hours and minutes components of the duration. Try it.)

Here the variable $x has a different value each time around the XQuery for loop. This feels a bit like an update. Technically though, each time round the for loop you're creating a new variable with a new value, rather than assigning a new value to the old variable.

Upvotes: 8

Matti Virkkunen
Matti Virkkunen

Reputation: 65156

No, you can't update a variable. If you can't rely on the order of execution, how would doing so make sense anyways?

Of course, even without updatable variables, you can do pretty much everything, including "loops" with incrementing variables, using a recursive function. Whether this is efficient or a good idea is another story. I once implemented sqrt() in XSLT (which also has non-updatable variables) using a recursive template...

Upvotes: 0

Related Questions