Previous: Evaluate Transforms

Libraries

We've been seeing the evaluate function a lot, like so:

evaluate ( source, transform, scope, library )

We know the source is the user's data they want transformed. We know the transform is the sUTL program that'll do the actual transforming. We know the scope varies throughout evaluation of the transform, although it's initialised to be the same as the source.

But, what's this library ey? We've only seen that it's a dictionary, empty in all the examples we've seen.

The library is a dictionary of transforms. The keys are the transforms names as they'll be used in your sUTL code, and the values are the transforms corresponding to those keys.

Say I have a library like this:

library = {
  "add2": ["&+", "^@.value", 2]
}

Then with this source:

source = {
    "value": 5
}

I could use it in a transform like this:

transform = {
    "!": "^*.add2",
    "value": {
      "!": "^*.add2"
    }
}

and get the result

result = 9

You see the notation "^*.add2"? That's a path into the library. Because the library's just a dictionary, this path simply evaluates to the transform add2.

So, when it's used in an evaluate transform, like this:

{
    "!": "^*.add2"
}

it evaluates add2 (which grabs "value" from the scope, adds 2 to it, and returns the result).

You could just construct a dictionary full of library transforms in code, and pass it as library to evaluate, but sUTL is designed to do something a little better.

Declarations

We've been seeing transforms throughout this document, but to really use the library we're going to need to mark up the transforms with some metadata. That's what a declaration is for.

A declaration looks like this:

{
    "name": "mylibraryfunction",
    "language": "sUTL0",
    "transform-t": <the transform>,
    "requires": [<declaration name prefix>, ...]
}

In this declaration, I've given my transform a name, I've declared the language version (at the moment always sUTL0), I've included my transform, and I've included references to any other transforms that my transform requires to be in the library.

For example, here's "add2" in a declaration:

{
    "name": "add2", 
    "language": "sUTL0", 
    "transform-t": [
        "&+", 
        "^@.value", 
        2
    ]
}

Using this, I can now refer to add2 in another declaration. Here's add4:

{
    "name": "add4_tmp", 
    "language": "sUTL0", 
    "transform-t": {
        "!": "^*.add2", 
        "value": {
            "!": "^*.add2"
        }
    },
    "requires": [
        "add2"
    ] 
}

Using this syntax, I can ensure that add4 will have add2 in its library when it is evaluated.

How we handle this in code is interesting. It uses a function called compilelib

compilelib ( declaration, distribution ) -> library

First, you load all your library declarations and put them in a list, called the distribution.

Then, you call compilelib, passing it in your declaration, and the distribution.

It returns you a library that you can pass to evaluate, including all the transforms from declarations that your declaration asked for in its "requires" section.

compilelib looks for each require by searching the distribution from the head to the tail. The first declaration it finds in the distribution whose name has the "require" as a prefix is chosen as a match. If no match is found, that entry is null.

So note that a library declaration can have a very long name (like "rawpathing_sutllang_emlynoregan_com"), but will match any prefix (like "rawpathing" or "rawpathing_sutllang"). 

Whatever name prefix you use in "requires" is the attribute name that will be used in the library. So, you must use the same name in a path statement in the transform. eg: if you used "requires": ["rawpathing_sutllang", ...], then you must use "^*.rawpathing_sutllang" in the transform.

compilelib also takes care of loading intralibrary dependencies. So if a library function has its own "requires" statement, everything there will be loaded from the distribution too, transitively.

In sUTL Studio this is taken care of behind the scenes. But we can see some of this in action. Let's try out add2 and add4 as above.

Here's add2. Note the long name. It has no requirements.

Now here's add4. It refers to add2 as "add2_sutllang".

Note how it uses "add2_sutllang" in both the requires section and the transform itself.

Let's make a new transform, addinspector, which requires add4 and shows us the library as the result.

Check out the result. You can see the library dictionary. Note that although we only required "add4_sutllang_emlyn", we also got add2, because it was required by add4.

The Builtin Evaluation Transform, revisited

We saw the builtin transform before, but it's slightly misnamed. It'll also evaluate a library transform. It'll choose a builtin first, but if there's no builtin with that name it'll try selecting from the library.

So, we could do a variation of add4 as follows:

The Evaluation transforms have been swapped for Builtin transforms, and you'll notice that the transform is referred to by name rather than using a path.  

Wrapup

The library mechanism is very powerful; it lets us build deeply nested and modular sUTL transforms. That'd be even better if there was already a useful distribution of pre-written transforms that we could use.

There is, it's called the core library, and it's documented in the next section.

Next: The Core Library