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

From Records to Objects #720

Closed
michaelhkay opened this issue Sep 25, 2023 · 26 comments · Fixed by #985
Closed

From Records to Objects #720

michaelhkay opened this issue Sep 25, 2023 · 26 comments · Fixed by #985
Labels
Discussion A discussion on a general topic. Feature A change that introduces a new feature XPath An issue related to XPath XQuery An issue related to XQuery

Comments

@michaelhkay
Copy link
Contributor

michaelhkay commented Sep 25, 2023

It has become idiomatic to use maps, and record type definitions, to declare a collection of functions; so for example the random-number-generator object offers a "method" next() that can be called using the syntax $rng?next().

The problem is that it's not possible, within the XPath/XQuery language, to implement such a function with implicit access to the object on which it is invoked. The implementation of the function does not have access to any kind of $this variable.

This issue considers how we can move forwards from supporting simple records to introduce object capabilities, in an incremental and compatible way.

Here are three steps in that direction:

  1. Where a named record type is declared, also create a corresponding constructor function. So if you declare

declare item type my:loc as record(longitude as xs:double, latitude as xs:double)

you also get a constructor function allowing my:loc(180, 180), allowing both positional or keyword arguments corresponding to the field names,

  1. Allow default values to be defined in the record type, which act as default values for the parameters in the constructor function.

  2. Allow functions that are defined as part of a record type access to a variable $this. The constructor function provides an implicit binding of this variable to the record/map/object that is being instantiated.

  3. Allow self-reference to a named record type (and its constructor function) within the record definition.

So you can now do:

declare type my:counter as record (
   value as xs:integer,
   increment := fn() as my:counter {my:counter($this?value + 1)}
)

and then

let $x := my:counter(0)
return $x?increment()?value

which returns 1.

@ChristianGruen ChristianGruen added XPath An issue related to XPath XQuery An issue related to XQuery Feature A change that introduces a new feature labels Sep 25, 2023
@dnovatchev
Copy link
Contributor

dnovatchev commented Sep 25, 2023

On first reading seems quite good.

Here are three steps in that direction:

In fact there are 4 of these 😄

Some observations:

  1. Object in any programming language is the top-most (root) type from which all possible other types inherit. However here it is proposed only to associate the concept of object to records (maps). This can be misleading/confusing. Maybe one more step would be necessary in order to make any item-type in the XDM an object. One way to do this is as per Maps with Infinite Number of Keys: Total Maps and Decorated maps #105 to represent any instance of any item-type as a map that has 0 regular keys and whose default-key ("\") has as its value exactly this instance.

  2. The variable name $this may already have been used in existing XPath 3.1 code of a function that is the value of some key of a map. Seems like using this name from now on to refer to the container-map itself might in some cases break that existing code. We could think of some special name (such that hasn't been allowed until now), such as $$this, or even $this() or fn:this()

  3. Similarly to XSLT, we could specify that one tunnel-parameter is passed to the members of a map, whose value is the map itself. Again, it is an open issue to find the best name for such parameter/function.

  4. It seems that the function fn:this() needs to be specified as being context-dependent. Doing so will allow a map instance to traverse the hierarchy of its ancestors:

  • fn:this( fn:this() ) evaluates to the map that contains the map that contains the map-value (grand-parent)
  • fn:this( fn:this( fn:this() ) ) evaluates to the great-grand-parent
  • and so on.
  1. Every instance of an object (that is every instance of every item-type) can (and maybe should) have a set of standard "properties" that are accessible under the value of a specially named key of the map. Thus, we could access any "system properties" and add any "user-defined properties" to these. Having this capability (as in most other languages) eliminates the need to declare and access such properties in other, unnatural ways, such as via "annotations".

@dnovatchev dnovatchev added the Discussion A discussion on a general topic. label Sep 25, 2023
@michaelhkay
Copy link
Contributor Author

Thanks for your comments.

Object in any programming language is the top-most (root) type from which all possible other types inherit. However here it is proposed only to associate the concept of object to records (maps).

Indeed, I am deliberately trying to make changes that are relatively modest in scope rather than redesigning the type system from the top down. I think small steps are more likely to be successful.

The variable name $this may already have been used in existing XPath 3.1 code of a function that is the value of some key of a map.

This is why I linked this functionality to the use of a constructor function for a map, which is a new capability. A regular function added to a map using map:put() does not have the magic $this behaviour which is available when a "member function" is declared as part of the item type definition.

Similarly to XSLT, we could specify that one [tunnel-parameter]( is passed to the members of a map

Tunnel parameters have dynamic scope. The $this variable is intended to be present in the static context of the relevant function body, and to be part of the non-local variable bindings of the function item within the map.

It seems that the function fn:this() needs to be specified as being context-dependent.

There's no such function. Rather there's a perfectly normal variable $this added to the non-local variable bindings of certain functions.

Doing so will allow a map instance to traverse the hierarchy of its ancestors:

No, because this proposal doesn't change the data model so that maps have parents and ancestors. Rather it provides a way of creating a map M in which one or more entries are function items that have a non-local variable binding $this whose value is M.

fn:this( fn:this() ) evaluates to the map that contains the map that contains the map-value

No, that's not the mechanism. I'm not proposing to change the fact that a map M can be added as an entry in many other maps, none of which is a unique or distinctive container or parent of M.

Every instance of an object (that is every instance of every item-type) can (and maybe should) have a set of standard "properties" that are accessible under the value of a specially named key of the map. Thus, we could access any "system properties" and add any "user-defined properties" to these.

In separate proposals I have indeed been advocating this capability. Since we already have the mechanism of annotations on function items, I have been proposing that we should build on the existing concept of annotations rather than inventing a new but very similar concept of system properties.

@dnovatchev
Copy link
Contributor

dnovatchev commented Sep 25, 2023

Thanks for your comments.

Object in any programming language is the top-most (root) type from which all possible other types inherit. However here it is proposed only to associate the concept of object to records (maps).

Indeed, I am deliberately trying to make changes that are relatively modest in scope rather than redesigning the type system from the top down. I think small steps are more likely to be successful.

Exactly due to this we should not use the term "object" which in all other languages means the root of the type hierarchy. If this is just for maps (records) we need a more modest name.

The variable name $this may already have been used in existing XPath 3.1 code of a function that is the value of some key of a map.

This is why I linked this functionality to the use of a constructor function for a map, which is a new capability. A regular function added to a map using map:put() does not have the magic $this behaviour which is available when a "member function" is declared as part of the item type definition.

It seems illogical why map-values (that are functions) can only in some cases have the $this variable, but cannot in other cases.

Similarly to XSLT, we could specify that one [tunnel-parameter]( is passed to the members of a map

Tunnel parameters have dynamic scope. The $this variable is intended to be present in the static context of the relevant function body, and to be part of the non-local variable bindings of the function item within the map.

if a map (record) is created via a constructor, then it has a limited scope and when it goes out of scope, so does its $this variable. Then why are we even mentioning "static-scope"?

It seems that the function fn:this() needs to be specified as being context-dependent.

There's no such function. Rather there's a perfectly normal variable $this added to the non-local variable bindings of certain functions.

Having a new standard function (this() or any other suitable name) completely eliminates the possibility of name conflicts in pre-existing code.

Doing so will allow a map instance to traverse the hierarchy of its ancestors:

No, because this proposal doesn't change the data model so that maps have parents and ancestors. Rather it provides a way of creating a map M in which one or more entries are function items that have a non-local variable binding $this whose value is M.

fn:this( fn:this() ) evaluates to the map that contains the map that contains the map-value

No, that's not the mechanism. I'm not proposing to change the fact that a map M can be added as an entry in many other maps, none of which is a unique or distinctive container or parent of M.

Agreed.

Every instance of an object (that is every instance of every item-type) can (and maybe should) have a set of standard "properties" that are accessible under the value of a specially named key of the map. Thus, we could access any "system properties" and add any "user-defined properties" to these.

In separate proposals I have indeed been advocating this capability. Since we already have the mechanism of annotations on function items,

No, we don't - only XQuery has annotations.

I have been proposing that we should build on the existing concept of annotations rather than inventing a new but very similar concept of system properties.

I find the concept of functions that are members of "objects" but have some properties that are defined outside of the object rather disturbing and confusing. Maybe this is result of using the term "object" - and this would be equally confusing and disturbing to any other software developer, who already knows what an "object" is supposed to mean.

Anyway, a function defined within an object should have all its properties as part of this defining object. Having some of its properties here and some of its properties there means that this function does not completely belong to (is not completely defined by) its object.

So much about an object having a function - all this concept is destroyed/blown-away by the fact that this function has some properties that are not part of its object.

To summarize:

We have 3 major differences of understanding this proposal:

  1. The naming of what is proposed to be a variable named $this that could create conflict with identically-named variables in pre-existing code. One solution proposed is to have this value not bound to any variable, but produced by a new standard function, thus no pre-existing code can have references to such (non-existent in the past) function, thus no conflict is possible.

  2. Whether or not all properties of a function that is defined within an object (and belongs to it) should also belong to that same object.

  3. The use of the term object for such a narrow realm as constructed records is in sharp contrast with everyone's traditional/consolidated/universally-established understanding of the meaning of "object" in any known programming language. It is proposed to use another, less-pretentious name that will not cause confusion and disturbing misunderstanding. Or, otherwise, these so called "objects" will not be "first-class objects of the language" 😄

@michaelhkay
Copy link
Contributor Author

On reflection, the proposal for implicitly binding a variable $this so that functions within a record can access the containing record has a significant problem: an operation like map:put() or map:remove() that constructs a new map would leave the variable $this pointing to the old map. This suggests that the magic has to work at the time the lookup operator in $map?action() is evaluated, rather than at the time the action() function is created.

This starts to feel a bit similar to the proposals in issue #596, where we propose that if a map that is marked as being "pinned", a lookup operation on the map will pass the identity of the map as a special property on the value that results from the lookup: in this case the action function. We then need a mechanism for the body of the action function to access the value of this property.

@dnovatchev
Copy link
Contributor

On reflection, the proposal for implicitly binding a variable $this so that functions within a record can access the containing record has a significant problem: an operation like map:put() or map:remove() that constructs a new map would leave the variable $this pointing to the old map. This suggests that the magic has to work at the time the lookup operator in $map?action() is evaluated, rather than at the time the action() function is created.

Yes, $this has correct meaning only when the (function's) value is explicitly retrieved either as $m?funcName or as $m("funcName") - that is, when the parent $m is known

@michaelhkay
Copy link
Contributor Author

michaelhkay commented Oct 9, 2023

A revised proposal (in its entirety):

  1. We recognise the annotation %method on an inline function.
  2. If a function is annotated as %method, then the static context for the function body includes an additional variable $this of type map(*)? and the resulting function item contains the non-local variable binding {$this := ()}. More specifically the inline function expression IFE is evaluated as if it were let $this := () return IFE.
  3. The semantics of the lookup operator are changed so that when the LHS is (or includes) a map $M, and the raw result of the lookup is (or includes) a function item $F that is annotated %method, then $F is replaced in the result with $F2, which differs from $F in that it has a non-local variable binding {$this := $M}.

The effect of this is that you can now do:

declare function r:rectangle($x, $y) as record(x, y, area) {
  map{"x":$x, "y":$y, "area": %method fn() {$this?x * $this?y}}
}

and r:rectangle(3, 5)?area() now returns 15.

We should add function annotations (or at least the %method annotation) to the XPath grammar.

A possible variation would be to add the magic treatment of %method functions to map:get($M, $key) and to $M($key) as well as to the function lookup operator. On the whole, though, I'm in favour of doing it only for the lookup operator.

@ChristianGruen
Copy link
Contributor

The ambition to introduce object-like structures in the language is definitely appealing; out of the box, I get many use cases in mind which could be realized much easier this way.

I have some general concerns that we overstrain our existing language features to “just make it work”. I understand the proposed rules, but I am afraid that some users could be overwhelmed by the interplay of the concepts that have originally been introduced for something else. Looking at…

declare function r:rectangle($x, $y) as record(x, y, area) {
  map{"x":$x, "y":$y, "area": %method fn() {$this?x * $this?y}}
}

…people might, among other things, need to understand that:

  • a map constructor is used to create a “record”.
  • with %method, a function is “transformed to a method”
  • $this always references a map

In the following, I’m proposing a syntax that feels more consistent and intuitive to me. I’m bold enough to ignore the implementation details. I’ve also skipped the idea of generating a constructor function for records (which I actually find attractive):

declare function rectangle($x as xs:integer, $y as xs:integer) as record(x, y, area) {
  record { x := $x, y := $y, area := fn { $this?x * $this?y } }
};
rectangle(4, 8)?area()

The ideas here:

To push this further:

declare type rectangle as record(
   x as xs:integer,
   y as xs:integer,
   area := $this?x * $this?y,
   *
);

declare function rectangle($x as xs:integer, $y as xs:integer) as rectangle {
  record rectangle { $x, $y, is-square := $this?x = $this?y }
};
  • If $this is defined within type declarations, we could use it to assign default (the default expressions would need to be evaluated after a record has been initialized)
  • If the record keyword is followed by the type name, we could assign the record values in the given order, as it’s known from function calls.
  • A record could dynamically be enriched with additional entries.

@dnovatchev
Copy link
Contributor

@ChristianGruen

I like your proposal as it is simple and clear.

Do I understand correctly that with this proposal we don't need any %method annotations?

I feel uneasy about introducing the annotations syntax in XPath. Annotations are just masked decorators, and when we have the power of decorators anyone can create any "annotations" that come to mind on the spur of the moment.

@ChristianGruen
Copy link
Contributor

ChristianGruen commented Oct 9, 2023

Do I understand correctly that with this proposal we don't need any %method annotations?

I would hope so. Michael has been much more explicit about implementational aspects, but I think it could be possible to prepend let $this := () return to all expressions in the constructor.

I'm not really thrilled about introducing new magic variables (imho it’s ugly to have them in the catch clause); maybe we find a better solution. I've seen yourc suggestion to use fn:this; I'll have more thoughts on it.

I feel uneasy about introducing the annotations syntax in XPath. Annotations are just masked decorators, and when we have the power of decorators anyone can create any "annotations" that come to mind on the spur of the moment.

I'm not sure. How would you specify the existing %private or %updating annotations with a decorator? – Well, we should discuss that in a separate issue.

@michaelhkay
Copy link
Contributor Author

michaelhkay commented Oct 9, 2023

@ChristianGruen I was trying to take things one step at a time: get the primitives in place first, then build syntactic sugar on top of them. I agree that record constructors would be useful (but should they be custom syntax, or named functions?), and that it might well be useful for functions within record constructors to behave this way by default. But the key thing I was trying to establish is: what exactly is the mechanism that causes $this to be bound to a particular map?

I don't think that's an "implementational aspect"; it's fundamental to the semantics, even though we might hope that we can devise a sufficiently intuitive syntax that most users won't need to think about it.

@dnovatchev
Copy link
Contributor

I'm not really thrilled about introducing new magic variables (imho it’s ugly to have them in the catch clause); maybe we find a better solution. I've seen yourc suggestion to use fn:this; I'll have more thoughts on it.

Yes, using a function instead of a variable eliminates the possibility for any naming conflicts with pre-existing code having a variable named $this, or if a user would want to give one of his variables exactly this name.

It would be best if we do not introduce a list of banned-names in the language

@ChristianGruen
Copy link
Contributor

@ChristianGruen I was trying to take things one step at a time

Makes absolute sense…

get the primitives in place first, then build syntactic sugar on top of them.

…but I think it's not only syntactic sugar. I am not sure if we should introduce method semantics or $this variables inside maps at all, or somewhere else outside records. I believe we should rather do our best to evolve the record concept. For implementors, maps and records may be pretty similar (and they could still be interchangeable at a low level). From a high-level perspective, it’s possible to regard them as something pretty different (data containers vs. objects with programming logic).

should they be custom syntax, or named functions?

Maybe yes… How would that look like?

But the key thing I was trying to establish is: what exactly is the mechanism that causes $this to be bound to a particular map?

I see. Could it work to add let $this := () return before all arguments of a record constructor?

We could also bind the currently generated record to the context ($M !):

declare function rectangle($x, $y) {
  record {
    x := $x,
    y := $y,
    is-square := ?x * ?y
  }
};

The resulting syntax would be similar to how record entries are accessed later on:

rectangle(4, 8)?x

@michaelhkay
Copy link
Contributor Author

The main advantage of using a variable is that we already have the concept of "non-local variable bindings" in function items, but of course we could expose the magic variable through function syntax if we prefer. Another benefit, however, is that at present the set of in-scope function names is constant throughout a module, whereas the set of variable names varies.

@michaelhkay
Copy link
Contributor Author

With syntax like this:

declare function rectangle($x, $y) {
record {
x := $x,
y := $y,
area := ?x * ?y
}
};

we need to be very clear what is the result of (rectangle(3, 4) => put('y', 6)) ? area().

The specification I proposed states clearly that with a function annotated as a method, the binding of $this happens at lookup time, which means that when a new map is created by modifying some fields, $this does not remain bound to the old version of the map.

@dnovatchev
Copy link
Contributor

With syntax like this:

declare function rectangle($x, $y) { record { x := $x, y := $y, area := ?x * ?y } };

we need to be very clear what is the result of (rectangle(3, 4) => put('y', 6)) ? area().

The specification I proposed states clearly that with a function annotated as a method, the binding of $this happens at lookup time, which means that when a new map is created by modifying some fields, $this does not remain bound to the old version of the map.

This will not be a problem if the specification mandates that map:put($someRecord, $someKey, $someValue)), where $someRecord is a record, creates a new record of the same type by calling its (of the new) record's constructor.

Because the new record's constructor is called, inside the constructor $this would be set correctly, and then can be used without any problems.

A purist may want to have a separate : record:put(...) but I don't think this is necessary. There was no record - type in the past, so specifying this additional behavior for record types doesn't affect any backwards compatibility.

@ChristianGruen
Copy link
Contributor

we need to be very clear what is the result of (rectangle(3, 4) => put('y', 6)) ? area().

It would probably be illegal, as area is no function, so the value would need to be retrieved with ?area:

(rectangle(3, 4) => map:put('y', 6)) ? area

Using the context value inside functions would be tricky indeed, unless the object is not passed on as argument… Which is already possible with maps:

let $rectangle := map { 'x': 3, 'y': 4, 'area': fn { ?x * ?y } }
return $rectangle ! map:put(., 'y', 6) ! ?area(.)

I can think of plenty of other questions. For example, what is going to happen if map:put($rect, 'y', 'string') is called? What happens if a variable or function is removed (map:remove($rect, 'x'), map:remove($rect, 'area'))? In other words, what happens if the resulting map does not match the record definition? Next, will it be intuitive that it is legal to modify “something like an object” with map functions?

I may be too hesitant here, but will we manage that the result will feel better than a proof of concept? Wouldn’t it be better in the long term (possibly in a later version of the language) to design a new function subtype for objects with (pseudo-)mutable and immutable components, and with dedicated functions exist to “modify” (i.e., create a new object with updated) mutable values?

@michaelhkay
Copy link
Contributor Author

@dnovatchev The concept "creates a new record of the same type" doesn't fit with the type system as currently defined. A record is (at present) simply a map, and it conforms to an infinite number of record types. Furthermore, adding a new entry to a map creates a map that doesn't necessarily conform to the same type as the original.

@michaelhkay
Copy link
Contributor Author

@ChristianGruen "I may be too hesitant here". I'm inclined to think we can achieve something useful and powerful without fundamental changes to the data model or type system. I think we have most of the necessary building blocks in place, we just need to assemble them in the right way.

@MarkNicholls
Copy link

MarkNicholls commented Oct 12, 2023

If I want a "this" parameter in my function, why don't I just declare it and pass it explictly?
Is there a use case where this isn't possible?
"It has become idiomatic to use maps, and record type definitions, to declare a collection of functions"
I see this more in terms of dictionary passing in an FP context, than objects in an OO context, especially as state in xslt is immutable.

@michaelhkay
Copy link
Contributor Author

If I want a "this" parameter in my function, why don't I just declare it and pass it explictly?

because it's more convenient to do $myrectangle?area() rather than $myrectangle?area($myrectangle).

@MarkNicholls
Copy link

MarkNicholls commented Oct 12, 2023

(caveat...I've only very recently started passing maps around with functions embedded in XSLT, its something I do a lot in other languages, including multi paradigm ones in preference to objects, but in XSLT I'm very much learning how to do this, so my views may be skewed by these other language contexts)

wrt your example, fair enough, to be honest, for me its not a massive cost.

Part of the pain in your example is viewing the record as an 'object' and then passing the dictionary to itself seems unnecessary.
i.e. If you view your data as objects, then all functionality looks like methods, and if your syntax doesnt support that, then it obviously feels clumsy...like the old hammer nail thing.

For me there are 2 dictionaries and the caller code would look like.

$rectangleTheory?area($myrectangle)

which doesnt look unreasonable, but I accept this is probably a minority view and highly subjective, and having records that support "this" doesnt preclude working in either way.

To me XSLT always feels more functional than OO to me (all that matching and immutability) so for me it grates slightly and "dictionary passing" seems more natural.

If the motivation is simply make caller code 'lighter' then maybe a new operator would be simpler

$myrectangle?>area()

that automagically passes the record into the function as the first parameter, then you don't need the baggage of objects, and have the convenience of OO like syntax...like extension methods in C#...I quite like extension methods...convenient syntax without all the object baggage.

@MarkNicholls
Copy link

MarkNicholls commented Oct 16, 2023

@michaelhkay I wanted to see how painful this was without 'objects'. 'fix' etc only need to be written once (I could not work out how to force saxon to be lazy, and I'm not convinced this state monad construct works in general, I feel I may be cheating), ignoring that, the cost was.

So contextually (i.e. we do this once ever):
a) not having local functions forced me to build the map function in xpath (I've never really done this sort of xpath before - I'd rather do xslt in xslt)
b) there's a certain amount of clunkiness here, probably due to my naive xslt/xpath.
c) wrestling with laziness i.e. making an eager language lazy (xsl:lazy would be nice).

but for the specific issue though:
a) making sure you write your 'object' constructor in sensible manner (its quite easy to have an infinite recursion if you evaluate 'this' at the wrong point) i.e. - you have to dereference 'this' inside the 'object method'
b) knowing that you need to use this technique to generate "this"...many wont know it.

it obviously isnt as easy as writing "object".

<xsl:stylesheet xmlns:xsl=http://www.w3.org/1999/XSL/Transform
    xmlns:xs=http://www.w3.org/2001/XMLSchema
    xmlns:kooks="kookerella.com"
    xmlns:map=http://www.w3.org/2005/xpath-functions/map
    exclude-result-prefixes="xs"
    version="3.1">

    <!-- (integer * integer) -> (LazyValue<Rectangle> -> Rectangle) -->   
    <xsl:function name="kooks:makeRectangle">
        <xsl:param name="height"/>
        <xsl:param name="width"/>
        
        <xsl:sequence select="
            function($lazyThis)
            {
                map 
                {
                    'width' : $height,
                    'height' : $width,
                    'area' : function() 
                    {
                        let $resultTuple := kooks:getValue($lazyThis),
                        $this := $resultTuple[1]                
                        return map:get($this,'height') * map:get($this,'width')
                    } 
                }
            }"/>
    </xsl:function>    
    
    <xsl:template match="/">
        <xsl:variable name="rectangle" select="kooks:fix(kooks:makeRectangle(10,20))"/>
        <root>
            <height>
                <xsl:value-of select="map:get($rectangle,'height')"/>
            </height>
            <width>
                <xsl:value-of select="map:get($rectangle,'width')"/>
            </width>
            <area>
                <xsl:value-of select="map:get($rectangle,'area')()"/>
            </area>
        </root>
    </xsl:template>    
    
    <!-- *************************************** contextual stuff ************************************ -->
    <!-- informal type -->
    <!-- type LazyValue<'a> = LazyValue of (Unit -> 'a * LazyValue<'a>) --> 
    
    <!-- LazyValue<'a> -> 'a * LazyValue<'a>  -->   
    <xsl:function name="kooks:makeValue">
        <xsl:param name="value"/>
        <xsl:sequence select="function()
            {
            ($value,kooks:makeValue($value))
            }"/>
    </xsl:function>
    
    <!-- LazyValue<'a> -> 'a * LazyValue<'a>  -->   
    <xsl:function name="kooks:getValue">
        <xsl:param name="lazyValue"/>
        <xsl:sequence select="$lazyValue()"/>
    </xsl:function>
    
    <!-- (LazyValue<'a> -> 'a) -> LazyValue<'a> -->   
    <xsl:function name="kooks:lazyFix">
        <xsl:param name="f"/>
        <xsl:sequence select="function() 
            {
            let $result := $f(kooks:lazyFix($f))
            return ($result,kooks:makeValue($result))
            }"/>
    </xsl:function>
    
    <!-- (LazyValue<'a> -> 'a) -> 'a -->   
    <xsl:function name="kooks:fix">
        <xsl:param name="f"/>
        <xsl:variable name="resultTuple" select="kooks:getValue(kooks:lazyFix($f))"/>
        <xsl:sequence select="$resultTuple[1]"/>        
    </xsl:function>
    
</xsl:stylesheet>

@dnovatchev
Copy link
Contributor

@MarkNicholls As for laziness, please see this: #670 (The trouble with XPath‘s fn:fold-right. A fix and Proposal for fn:fold-lazy) and this: #716 (Generators in XPath)

@MarkNicholls
Copy link

MarkNicholls commented Oct 16, 2023

@dnovatchev - I looked at that (in some blog i think), it made my head hurt, I'm a novice of 2 days of anything but 'paths' in xpath - I needed something much more trivial, I just wanted to declare an expression as lazy, then i could implement fix, then i can fix my constructor introduce 'this'. The idea was just to at least measure the pain (for me at least), before considering the remedy.
If theres a blog about how to make a simple expression lazy then I'd be interested, else I'm stuck with my state tuple.

... actually 716 does make sense.....I had hoped that xsl:iterate was just such a (deferred at least) construct, but it seemed eager so I rolled my own.

I think i could probably roll it in this style.

@ChristianGruen
Copy link
Contributor

See #916 (comment) for some feedback on Michael’s proposal (https://qt4cg.org/pr/916/xquery-40/xquery-40-autodiff.html).

@dnovatchev
Copy link
Contributor

I hope it is still not too-late.

I propose to have a simple syntax for invoking a "member-function" with key myFunName on a map $m like this:

$m ?> myFunName({any-arguments-here})

The rules are simple:

  1. The left-hand-side (LHS) must be a map.

  2. The right-hand-side (RHS) must be a key-name of the map specified by the LHS.

  3. The value of the key-name specified by the RHS must be a function.

  4. The function will be invoked with a first, implicitly-provided argument, which is $m

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Discussion A discussion on a general topic. Feature A change that introduces a new feature XPath An issue related to XPath XQuery An issue related to XQuery
Projects
None yet
4 participants