MarkLogic XQuery 尾调用优化

MarkLogic XQuery tail call optimization

我重写了一个溢出堆栈的递归函数以使用累加器,因为我认为 ML 正在优化这种模式。但是,我仍然收到堆栈溢出异常。这个函数在尾调用优化方面有什么问题吗?

xquery version "1.0-ml";

declare variable $BATCH-DURATION := xs:dayTimeDuration("P5D");

declare function local:start-times(
  $datetime-start as xs:dateTime,
  $datetime-end as xs:dateTime,
  $acc as xs:dateTime*
) as xs:dateTime+
{
  if (($datetime-end - $datetime-start) le $BATCH-DURATION) then $acc
  else local:start-times($datetime-start + $BATCH-DURATION, $datetime-end, ($acc, $datetime-start))
};

local:start-times(xs:dateTime('1800-01-01T17:45:42'), xs:dateTime('2017-10-10T17:45:42'), ())[last()]

我相信尾调用优化只有在函数的 return 值是无类型的情况下才有效。

即试:

declare function local:start-times(
  $datetime-start as xs:dateTime,
  $datetime-end as xs:dateTime,
  $acc as xs:dateTime*
)
{

希望对您有所帮助。

除了 Erik (@ehennum) 的中肯回答之外,关于代码可能还有更多要说的。即使需要递归,也可能有更聪明的做事方式。这里有一些带有指标的替代算法:

xquery version "1.0-ml";

declare variable $BATCH-DURATION := xs:dayTimeDuration("P5D");

declare function local:start-times(
  $datetime-start as xs:dateTime,
  $datetime-end as xs:dateTime,
  $acc as xs:dateTime*
)
{
  if (($datetime-end - $datetime-start) le $BATCH-DURATION) then $acc
  else local:start-times($datetime-start + $BATCH-DURATION, $datetime-end, ($acc, $datetime-start))
};

declare function local:start-times2(
  $datetime-start as xs:dateTime,
  $datetime-end as xs:dateTime
)
{
  if (($datetime-end - $datetime-start) le $BATCH-DURATION) then ()
  else 
    let $tick := $datetime-start + $BATCH-DURATION
    return ($tick, local:start-times2($tick, $datetime-end))
};

let $datetime-start := xs:dateTime('1800-01-01T17:45:42')
let $datetime-end := xs:dateTime('2017-10-10T17:45:42')
return (

  (: original recursive method :)
  let $start-time := xdmp:elapsed-time()
  return (
    local:start-times($datetime-start, $datetime-end, ())[last()],
    xdmp:elapsed-time() - $start-time
  ),

  (: more efficient recursive methode :)
  let $start-time := xdmp:elapsed-time()
  return (
    local:start-times2($datetime-start, $datetime-end)[last()],
    xdmp:elapsed-time() - $start-time
  ),

  (: non-recursive method:)
  let $start-time := xdmp:elapsed-time()
  let $datetime-closest :=
    $datetime-start + (fn:floor(($datetime-end - $datetime-start) div $BATCH-DURATION) * $BATCH-DURATION)
  return (
    (: handle edge case :)
    if ($datetime-closest eq $datetime-end) then
      $datetime-closest - $BATCH-DURATION
    else
      $datetime-closest,
    xdmp:elapsed-time() - $start-time
  )
)

HTH!