[xsl] Re: higher-order functions in xslt

Subject: [xsl] Re: higher-order functions in xslt
From: Dimitre Novatchev <dnovatchev@xxxxxxxxx>
Date: Sun, 25 Nov 2001 12:13:16 -0800 (PST)
> Being impressed with Dimitre's generic templates I sunk a little in a functional
> programming and here is my q: Is there a way to implement higher order functions 
> in xslt (I mean generating new functions, returning them as a result and applying
> them)?
> 
> ---
> Oleg Tkachenko,
> Multiconn International, Israel 

Hi Oleg,

Yes, using generic templates we can generate new functions and return them, so that
they can be applied later.

Here's an example, taken right from the book of Simon Thompson "Haskell: The Craft
of Functional Programming".

Suppose we are to build a calculator for numerical expressions. As part of our
system we need to be able to model the current values of the user-defined variables,
which we might call ***the store*** of the calculator.

To model the store, we define an abstract data type (ADT), which has the following
signature:

initial :: Store
value   :: Store -> Var -> Int
update  :: Store -> Var -> Int -> Store

The function "initial" produces an initial (empty) store.
The function "value" retrieves from the store the value (Int) of a variable name.
The function "update" updates the store with a new (variable-name, value)
association.

This ADT can have different implementations, e.g. by keeping an internal list of
couples (varName, value), or by implementing a function that given a variable name
returns its value.

The latter implementation is defined in Haskell in the following way:

newtype Store = Sto (Var -> Int)

initial :: Store
initial =  Sto (\v -> 0)

value   :: Store -> Var -> Int
value (Sto sto) v = sto v

update  :: Store -> Var -> Int -> Store
update (Sto sto) v n 
  = Sto (\w -> if v == w then n else sto w) 

Under this implementation:
  - the initial store maps every variable to 0.
  - to look up a value of a variable v the store function sto is applied to v
  - in the case of an update, a function returned is identical to sto except on the
variable, whose value is changed.

Here's the corresponding XSLT implementation:

store.xsl
---------
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:getInitialStore="f:getInitialStore"
xmlns:getValue="f:getValue0"
xmlns:upd-getValue="f:upd-getValue"
xmlns:updateStore="f:updateStore"
>
  
  <xsl:template name="getInitialStore" match="getInitialStore:*">
    <store>
      <getInitialStore:getInitialStore/>
      <getValue:getValue/>
      <updateStore:updateStore/>
    </store>
  </xsl:template>
  
  <xsl:template match="getValue:*">
     <xsl:value-of select="0"/>
  </xsl:template>
  
  <xsl:template match="updateStore:*">
    <xsl:param name="pName"/>
    <xsl:param name="pValue"/>
    
    <store>
      <getInitialStore:getInitialStore/>
      <upd-getValue:getValue>
        <store><xsl:copy-of select="../*"/></store>
        <name><xsl:value-of select="$pName"/></name>
        <value><xsl:value-of select="$pValue"/></value>
      </upd-getValue:getValue> 
      <updateStore:updateStore/>
    </store>
  </xsl:template>
  
  <xsl:template match="upd-getValue:*">
    <xsl:param name="pName"/>
    
    <xsl:choose>
      <xsl:when test="$pName = name">
        <xsl:value-of select="value"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="store/*[local-name()='getValue']">
           <xsl:with-param name="pName" select="$pName"/>
        </xsl:apply-templates>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Let's test it.

testStore.xsl
-------------
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
>

  <xsl:import href="store.xsl"/>
  
  <xsl:output method="text"/>  

  <xsl:template match="/"> 
    
    <!-- Get Initial Store -->
    <xsl:variable name="vrtfStore0">
      <xsl:call-template name="getInitialStore"/>
    </xsl:variable>
    
    <xsl:variable name="vStore0" select="msxsl:node-set($vrtfStore0)/*"/>
    
    <!-- Get A, B, C from the initial store: must be 0-s -->
      vStore0:
      A='<xsl:apply-templates select="$vStore0/*[local-name() = 'getValue']">
          <xsl:with-param name="pName" select="'A'"/>
         </xsl:apply-templates>'
      B='<xsl:apply-templates select="$vStore0/*[local-name() = 'getValue']">
          <xsl:with-param name="pName" select="'B'"/>
         </xsl:apply-templates>'
      C='<xsl:apply-templates select="$vStore0/*[local-name() = 'getValue']">
          <xsl:with-param name="pName" select="'C'"/>
         </xsl:apply-templates>'

    <!-- Update store0 with 'A=1' -->
    <xsl:variable name="vrtfStore1">
      <xsl:apply-templates select="$vStore0/*[local-name() = 'updateStore']">
        <xsl:with-param name="pName" select="'A'"/>
        <xsl:with-param name="pValue" select="1"/>
      </xsl:apply-templates>
    </xsl:variable>

    <xsl:variable name="vStore1" select="msxsl:node-set($vrtfStore1)/*"/>
    <!-- Get A, B, C from the store1: A is 1, the rest must be 0-s -->
      vStore1:
      A='<xsl:apply-templates select="$vStore1/*[local-name() = 'getValue']">
          <xsl:with-param name="pName" select="'A'"/>
         </xsl:apply-templates>'
      B='<xsl:apply-templates select="$vStore1/*[local-name() = 'getValue']">
           <xsl:with-param name="pName" select="'B'"/>
         </xsl:apply-templates>'
      C='<xsl:apply-templates select="$vStore1/*[local-name() = 'getValue']">
           <xsl:with-param name="pName" select="'C'"/>
         </xsl:apply-templates>'
    
    <!-- Update store1 with 'B=2' -->
    <xsl:variable name="vrtfStore2">
      <xsl:apply-templates select="$vStore1/*[local-name() = 'updateStore']">
        <xsl:with-param name="pName" select="'B'"/>
        <xsl:with-param name="pValue" select="2"/>
      </xsl:apply-templates>
    </xsl:variable>

    <xsl:variable name="vStore2" select="msxsl:node-set($vrtfStore2)/*"/>
    <!-- Get A, B, C from the store2: A is 1, B is 2, the rest must be 0-s -->
      vStore2:
      A='<xsl:apply-templates select="$vStore2/*[local-name() = 'getValue']">
           <xsl:with-param name="pName" select="'A'"/>
         </xsl:apply-templates>'
      B='<xsl:apply-templates select="$vStore2/*[local-name() = 'getValue']">
           <xsl:with-param name="pName" select="'B'"/>
         </xsl:apply-templates>'
      C='<xsl:apply-templates select="$vStore2/*[local-name() = 'getValue']">
           <xsl:with-param name="pName" select="'C'"/>
         </xsl:apply-templates>'
    
    <!-- Update store2 with 'C=3' -->
    <xsl:variable name="vrtfStore3">
      <xsl:apply-templates select="$vStore2/*[local-name() = 'updateStore']">
        <xsl:with-param name="pName" select="'C'"/>
        <xsl:with-param name="pValue" select="3"/>
      </xsl:apply-templates>
    </xsl:variable>

    <xsl:variable name="vStore3" select="msxsl:node-set($vrtfStore3)/*"/>
    <!-- Get A, B, C from the store3: A is 1, B is 2, C is 3,the rest must be 0-s
-->
      vStore3:
      A='<xsl:apply-templates select="$vStore3/*[local-name() = 'getValue']">
           <xsl:with-param name="pName" select="'A'"/>
         </xsl:apply-templates>'
      B='<xsl:apply-templates select="$vStore3/*[local-name() = 'getValue']">
           <xsl:with-param name="pName" select="'B'"/>
         </xsl:apply-templates>'
      C='<xsl:apply-templates select="$vStore3/*[local-name() = 'getValue']">
           <xsl:with-param name="pName" select="'C'"/>
         </xsl:apply-templates>'
   </xsl:template>
</xsl:stylesheet>

When applied on any xml source, the above transformation produces the following
result:


      vStore0:
      A='0'
      B='0'
      C='0'

    
      vStore1:
      A='1'
      B='0'
      C='0'
    
    
      vStore2:
      A='1'
      B='2'
      C='0'
    
    
      vStore3:
      A='1'
      B='2'
      C='3'
   
What is important to note in this example is that on every call of the updateStore
function, it creates the template reference for a new getValue function, like this:

      <upd-getValue:getValue>
        <store><xsl:copy-of select="../*"/></store>
        <name><xsl:value-of select="$pName"/></name>
        <value><xsl:value-of select="$pValue"/></value>
      </upd-getValue:getValue> 

This template reference always initiates the same template rule, ***but for a
different function***.

This function may call a chain of previously defined getValue functions for the
previos stores, until the first occurence of a variable name is found, or if not,
then finally the initial getValue function instantiates another template rule to
return the value 0.

There are two important lessons from this example:

 1. We demonstrated the dynamic creation of new functions, and their later
application to produce the necessary results.

 2. A function is not just "a template", but a template + context. The same template
may implement different functions, given different context.

Hope this helped.

Cheers,
Dimitre Novatchev.


















__________________________________________________
Do You Yahoo!?
Yahoo! GeoCities - quick and easy web site hosting, just $8.95/month.
http://geocities.yahoo.com/ps/info1

 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread