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

720 Allow methods in maps with access to $this #916

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions specifications/xquery-40/src/expressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9614,6 +9614,155 @@ return $incrementors[2](4)]]></eg>
</div4>
</div3>

<div3 id="id-methods" diff="add" at="issue720">
<head>Methods</head>

<p><termdef id="dt-method" term="method">A <termref def="dt-function-item"/> having the
annotation <code>%method</code> is referred to as a <term>method</term>. Methods are
intended primarily to be used as values within maps, and they allow the body of the function
item to refer to the containing map using the special variable <code>$this</code>.</termdef></p>

<ednote><edtext>To make this capability available in XPath as well as XQuery, we need to
add support for annotations to XPath.</edtext></ednote>

<p>The following example illustrates the use of a method that calculates the area of a
rectangle:</p>

<example id="id-example-rectangle-method">
<head>Using a method to compute the area of a rectangle</head>
<p>The following XQuery function definition can be used to construct a value
that represents a rectangle with a given height and width:</p>
<eg><![CDATA[declare function geo:rectangle(
$height as xs:double, $width as xs:double)
as record(height as xs:double,
width as xs:double,
area as %method function() as xs:double) {
map{"height": $height,
"width": $width,
"area": %method function(){$this?height * $this?width}
}
};]]></eg>
<p>A rectangle can then be constructed with a call such as:</p>
<eg>let $rect := geo:rectangle(2, 5)</eg>
<p>and its area can then be computed with the expression:</p>
<eg>return $rect?area()</eg>
<p>which returns the value 10.</p>

</example>

<p>When a function (either an XQuery function definition or an inline function) is created
with the annotation <code>%method</code>, the static context for the body of the function
is augmented with an in-scope variable named <code>$this</code> that
has the static type <code>map(*)</code>.</p>

<p>Similarly, the <term>captured context</term> of a function item having this annotation is
augmented with a binding of the variable <code>$this</code>.</p>

<p>A <termref def="dt-method"/> may be in either of two states: <emph>dormant</emph>
or <emph>active</emph>. When a method is first created (for example by evaluating an
inline function expression with the annotation <code>%method</code>, or by evaluating
a named function reference that identifies a function definition with this annotation),
the method will be dormant, which means that the binding of the variable <code>$this</code>
is absent; any attempt to call a dormant method results in a dynamic error due to this variable
being unbound. [Error TBA]</p>

<p>A method becomes active (that is, it acquires a binding for the <code>$this</code>
variable) when it is returned in the result of a lookup expression
applied to a map. For example, if the map <code>$rect</code> includes an entry whose key is
<code>"area"</code> and whose associated value is a dormant method, then the expression
<code>$rect?area</code> returns an active method, whose properties are identical to those of the
dormant method, except that the captured context is augmented with a binding of the variable
<code>$this</code> to the map <code>$rect</code>. In consequence, a dynamic function call
such as <code>$rect?area()</code> evaluates the <code>area</code> method with <code>$this</code>
bound to the map <code>$rect</code>, enabling the body of the method to refer to other entries
in the map.</p>

<p>The activation of the method happens when it is retrieved using any of the
following constructs:</p>
<ulist>
<item><p>A shallow lookup expression: <code>$rect?area</code>, regardless of the form
of the key specifier (for example, it might be <code>$rect?*</code> or
<code>$rect?(lower-case("AREA"))</code>).</p></item>
<item><p>A deep lookup expression: <code>$rect??area</code> (similarly).</p></item>
</ulist>



<p>It happens only when selecting a map entry whose value is a method as a singleton item
(and not, for example, a sequence or array having a method within its content).</p>

<p>It does not happen as a result of other operations on a map, such as call on
<code>map:get</code> (for example <code>map:get($rect, "area")</code>,
or a dynamic function call applied to the map (such as <code>$rect("area")</code>).</p>

<p>The value of the variable <code>$this</code> is set to the containing map
regardless whether the method is dormant or active: that is, any existing
binding of the variable is replaced.</p>

<note><p>Activation (binding of <code>$this</code>) happens when the method is
accessed in the map, not when it is added to the map. This is to ensure that when a new
map is constructed using operations such as <code>map:put</code> or <code>map:merge</code>,
methods copied into the new map do not retain a binding to the old map; rather, when the methods
are accessed, <code>$this</code> will always refer to the map from which they were retrieved.</p></note>

<note><p>A method that is retrieved from a map without being immediately called
(for example, <code>let $rect-area := $rect?area ...</code>) retains a binding to the map
from which it was retrieved, since it is the lookup operation that activates the method, not the
method call.</p></note>

<example id="id-example-method-updating">
<head>Updating methods</head>
<p>The following example demonstrates how a method might be used to increase
the size of a rectangle by a given factor.</p>

<eg><![CDATA[declare item type geo:rect as record(
height as xs:double,
width as xs:double,
resize as %method function(xs:double) as geo:rect);
declare function geo:rectangle(
$height as xs:double, $width as xs:double)
as geo:rect {
map{"height": $height,
"width": $width,
"resize": %method function($factor as xs:double){
geo:rectangle($this?height * $factor, $this?width * $factor}
}
};]]></eg>

<p>Then the call <code>geo:rectangle(3, 2)?resize(2)</code> returns a rectangle
of size 6 by 4.</p>

<ednote><edtext>Depends on resolution of issue 296 (recursive record types)</edtext></ednote>

</example>

<p>Methods can be used to deliver recursive inline functions, as shown in the example below.</p>

<example id="id-example-method-recursve">
<head>Recursive methods</head>
<p>The following example implements an anonymous function that computes
the product of sequence of numbers:</p>

<eg><![CDATA[let $product :=
map{"_product": %method fn($in) {
if (empty($in))
then 1
else head($in) * $this?_product(tail($in))}
}?_product
return $product((1.05, 1.04, 1.03))]]></eg>

<p>The next example illustrates a library of functions that are mutually
recursive:</p>

<eg><![CDATA[let $lib :=
map{"even": %method fn($val) {$val = 0 or $this?odd(abs($val)-1)},
"odd": %method fn($val) {not($this?even($val))}
}
return $lib?odd(23)]]></eg>
</example>

</div3>


</div2>

Expand Down