Reflection Extensions

Introduction

I've checked a new set of extensions into CVS. It's reflection, and here's a quick run-down for the brave who want to try it out (it's very alpha) and the curious who want to know what's coming.

Reflection howto

There's a single function defined, called program(), that returns an XML tree representing all types and functions in the program. The reflection types all inherit from Element, which makes it easy to query them using XPath. Here's an example how:


<o:set match="ref:program()/type[@name = 'String']/function[@name ='match']"/>

ref:program() gets the whole tree
/type[@name = 'String'] gets the String type
/function[@name ='match']"/> gets the match() function(s)

(but remember that there are _two_ String.match() functions!)

the reflection tree looks roughly this:


<type name="TypeName">
  <parent name="TypeName"/> *
  <function name="FunctionName"> *
    <param type="TypeName"/> *
  </function>
</type>

so types can be selected based on parent types, functions based on parameters and parameter types.

once you've got hold of one or more functions you can invoke methods with Function.call(arg1, arg2, ...)

note that for type functions (the only ones in the reflection tree at the moment) the first argument to call() must be the node instance that you want to invoke the function on! so if the function itself takes one parameter, you need to pass two to call().

Function.binds() returns all the Parameter nodes that have to be passed to Function.call(), so for type functions that is one more than for 'normal' functions.

there's another way to set parameter values: Function.bind(arg1, arg2, ...)

bind() returns a new function, one where arg1, arg2 etc are already bound to specific values.

time for some examples:


<o:set fun1="$prog//function[@name='match' and count(param) = 1]"/>
<-- get hold of String.match(pattern) -->

<o:eval select="$fun1.call('waka waka', '[wa]+')"/>
 invokes the function -
 the first arg to call() is the node instance,
 the second arg is the 'pattern' parameter.
 so that would be exactly the same as:
<o:eval select="'waka waka'.match('[wa]+')"/>

now let's try bind():

<o:set fun2="$fun1.bind('waka waka')"/>
 creates a new function that binds only one more parameter, because the
 node instance is now bound

<o:eval select="$fun2.call('\w+')"/>

so now the value of  count($fun1.binds())  is two,
but count($fun2.binds()) is one.

that's pretty much it for functions.

Higher Order Functions

Now for an example of higher order functions!

A higher order function is one that takes one or more functions as parameter.

apply() takes two parameters, the first one must be a function that binds exactly one argument. This function (the first parameter) is then applied to every node of the second parameter, which should be a nodeset.


<o:function name="apply">
  <o:param name="fun"/>
  <o:param name="list"/>
  <o:do>
    <o:for-each select="$list">
      <o:eval select="$fun.call(.)"/>
    </o:for-each>
  </o:do>
</o:function>

<-- create a nodeset of numbers -->
<o:variable name="numbers"/>
<o:do select="$numbers.add(2134)"/>
<o:do select="$numbers.add(1324)"/>
<o:do select="$numbers.add(2124)"/>

<-- get hold of Number.log() -->
<o:set log="ref:program()/type[@name='Number']/function[@name='log']"/>

<-- invoke Number.log() on all the numbers in the nodeset -->
<o:set numbers="apply($log, $numbers)"/>

okay, so we could have achieved the same thing with
<o:set numbers="$numbers.log()"/>
(why? because a function invoked on a nodeset will be invoked on every
node in the set)
but that wouldn't have illustrated higher order functions!

If you want to try this out, download a recent version of ObjectBox (0.9.7 or newer) and enable the extension by adding this to the command line:

-e org.oXML.extras.reflection.ReflectionExtensions

the namespace for ref:program() is http://www.o-xml.org/lang/reflection

have fun!