2

I try to write a simple function to provide me with a random letter each time i call it but I have difficulties combining my idea with the concept of a functional programing approach. Some help along the way would be appreciated! The code I have looks like:

<xd:doc>
        <xd:desc>Provides one random letter, if the type is provided it returns a letter of thet type</xd:desc>
        <xd:param name="type">The type of letter to return, one of (A,a,B,b)</xd:param>
    </xd:doc>
    <xsl:function name="gdpr:randomLetter" as="xs:string">
        <xsl:param name="type" as="xs:string"></xsl:param>
        <xsl:choose>
            <xsl:when test="$type = 'A'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 7)[1]"/>
                <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:when test="$type = 'a'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 7)[1]"/>
                <xsl:variable name="letters" select="('a','o','u','e','i','y','q')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:when test="$type = 'B'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 19)[1]"/>
                <xsl:variable name="letters" select="('W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:when test="$type = 'b'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 19)[1]"/>
                <xsl:variable name="letters" select="('w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 52)[1]"/>
                <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q','a','o','u','e','i','y','q','w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z','W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:otherwise>
        </xsl:choose>

    </xsl:function>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110

4 Answers4

5

Your question encapsulates the problem:

I try to write a simple function to provide me with a random letter each time i call it

But a function that produces different results on different invocations (with the same arguments) is not a true ("pure") function.

One way out of this is to exploit the fact that XSLT already has "impure" functions of a kind: a function that creates a new node returns a different node each time, and you can expose this by using generate-id(). So you could write

<xsl:function name="my:random" as="xs:double">
  <xsl:variable name="dummy"><a/></xsl:variable>
  <xsl:sequence select="fn:random-number-generator(generate-id($dummy))?permute(1 to 10)"/>
</xsl:function>

The only problem with this is that you're right on the boundaries of what's well-defined in the spec and the optimizer might not let you get away with such tricks. It's much better, if you can, to find some way of passing a different argument to the function each time it is called: for example, a sequence number, or generate-id() applied to the input node you are currently processing.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • 1
    I also like this answer: if you want side effects in a declarative enviroment, you will need to encode an state type yourself. Further reading https://stackoverflow.com/questions/3850368/how-do-functional-languages-model-side-effects – Alejandro Apr 25 '19 at 22:00
2

In the context of XSLT 3, I think one way to have a "new" random-number-generator for every node you need it is to define an accumulator:

<xsl:accumulator name="rng" as="map(xs:string, item())" initial-value="random-number-generator(current-dateTime())">
    <xsl:accumulator-rule match="foo[@type]" select="$value?next()"/>
</xsl:accumulator>

So that way you could implement your function as

<xsl:function name="gdpr:randomLetter" as="item()*">
    <xsl:param name="type" as="xs:string"/>
    <xsl:param name="rng" as="map(xs:string, item())"/>
    <xsl:choose>
        <xsl:when test="$type = 'A'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 7)[1]"/>
            <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:when test="$type = 'a'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 7)[1]"/>
            <xsl:variable name="letters" select="('a','o','u','e','i','y','q')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:when test="$type = 'B'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 19)[1]"/>
            <xsl:variable name="letters" select="('W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:when test="$type = 'b'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 19)[1]"/>
            <xsl:variable name="letters" select="('w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 52)[1]"/>
            <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q','a','o','u','e','i','y','q','w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z','W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:otherwise>
    </xsl:choose>        
</xsl:function>

and then call it with e.g.

<xsl:template match="foo[@type]">
    <xsl:copy>
        <xsl:value-of select="gdpr:randomLetter(@type, accumulator-before('rng'))"/>   
    </xsl:copy>
</xsl:template>

and make sure you use

<xsl:mode on-no-match="shallow-copy" use-accumulators="rng"/>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
0

The problem is that fn:random-number-generator function is deterministic. The specs itself explain that:

Both forms of the function are ·deterministic·: calling the function twice with the same arguments, within a single ·execution scope·, produces the same results.

You need to use properly the function under the key next contained in the resulted map from calling the random-number-generator function. Like the spec said:

The entry with key "next" is a zero-arity function that can be called to return another random number generator.

Alejandro
  • 1,882
  • 6
  • 13
  • Thank you Alejandro, that i have read and understood. However I could use some assistance in what would be the way to properly use the next function. A small example would be beneficial. – Erik Zander Apr 24 '19 at 18:24
  • 1
    @ErikZander, you basically have to write a function taking a random number generator as its argument and calling itself recursively with the `$rng?next()` value, like the specification does in the example function `r:random-sequence` or like Michael Kay does in the comment to https://stackoverflow.com/a/41586171/252228 with `fold-left`: `fold-left(1 to 10, random-number-generator(), function($z, $i){ head($z)?next(), tail($z), head($z)?number})` to keep further processing possible by using the `head()` of that call or by stopping it using the `tail()`. – Martin Honnen Apr 24 '19 at 18:39
0

For the sake of completeness, I came up with this solution but it only works for small pieces of text due to the depth of recursion.

On a sidenote- I realized my solution was waste of time as I use exist-db that does not include random-number-generator in it's XSLT implementation.

<xsl:function name="gdpr:rngRecurseStart">
     <xsl:param name="text"></xsl:param>
     <xsl:variable name="chars" select="functx:chars($text)"/>

     <xsl:copy-of select="gdpr:rngRecurse($chars,random-number-generator(current-dateTime()),'')"></xsl:copy-of>
 </xsl:function>

 <xsl:function name="gdpr:rngRecurse">
     <xsl:param name="chars"></xsl:param>
     <xsl:param name="rngGenerator"></xsl:param>
     <xsl:param name="newText"></xsl:param>
     <xsl:variable name="curentchar" select="$chars[1]"></xsl:variable>
     <xsl:variable name="newRngGenerator" select="$rngGenerator?next()"/>
     <xsl:choose>
         <xsl:when test="count($chars) >1">
             <xsl:variable name="transformedChar" select="gdpr:randomLetter2($newRngGenerator,$curentchar)"/>
             <xsl:variable name="resultText" select="concat($newText, $transformedChar)"/>
             <xsl:copy-of select="gdpr:rngRecurse(subsequence($chars,2),$newRngGenerator,$resultText)"></xsl:copy-of>
         </xsl:when>
         <xsl:when test="count($chars) =1">
             <xsl:variable name="transformedChar" select="gdpr:randomLetter2($newRngGenerator,$curentchar)"/>
             <xsl:variable name="resultText" select="concat($newText, $transformedChar)"/>
             <xsl:copy-of select="$resultText"></xsl:copy-of>
         </xsl:when>
         <xsl:otherwise><xsl:copy-of select="$newText"></xsl:copy-of></xsl:otherwise>
     </xsl:choose>

 </xsl:function>
  • It seems you should be able to use the `fold-left` pattern suggested by Michael Kay e.g. `fold-left(functx:chars($text), random-number-generator(), function($z, $c){ head($z)?next(), tail($z), gdpr:randomLetter2(head($z), $c)}) => tail() => string-join()`. – Martin Honnen Apr 30 '19 at 14:22
  • Yes that would most likely work, I just managed with what I completed so have not had time to check thou. But your solution certainly looks slik and I thank you for it ! Have to try it out if I have to revisit this. @MartinHonnen – Erik Zander May 17 '19 at 14:27