Previous: Unstructured Builtins

Evaluate Transforms

Evaluate Transforms are where you can really control evaluation, and write serious programs in sUTL. Builtins are the raw material, but we can build really powerful transforms by combining them into complex combinations using evaluate transforms.

The evaluate transform is a dictionary transform, and is processed like one (ie: all entries have their values evaluated as transforms), and then takes the following form:

evaluate transform = {
  "!": <sub-transform>
  arg1: <data>,
  arg2: <data>,
  ...
  argn: <data>
}

This transform is evaluated as follows:

evaluate ( source, transform, scope, library ) 
= evaluate ( source, sub-transform, scope-update ( scope, transform ), library )

scope-update is defined in unstructured builtins.  

What's happening is that the result of this transform is the result of evaluating the sub transform, in the context of the scope as modified by the other attributes. This is the same as what happens with unstructured builtins, but instead of ultimately evaluating a builtin, you're evaluating another transform.

Use case 1: Evaluate something you've constructed

Say we'd like to sum a list which is the source. The "+" builtin should do this, but the dictionary form doesn't evaluate the "args" argument (a bug which needs rectifying) and the list form requires the "+" builtin as the first element of the list.

Given this source:

source = [ 1, 2, 3, 4 ]

you might like to use this transform:

transform = {
    "&": "+",
    "args": "^@"
}

but unfortunately it doesn't evaluate the path, you just get the literal path out as the result.

Or you could try the list form:

transform = [
    "&+",
    "^@"
]

but that'll nest the values list inside the transform list; it needs to be flattened.

Try it:

What we can try is to construct the transform we want with the correct values already in place, then evaluate the transform explicitly.

If you look at sumtransform, it constructs a list transform that'll sum up the source list, by concatenating the "&+" operator on the front. In the result, you can see this constructed transform.

Then if you look at sum, it's the same thing as sumtransform, but it is wrapped in an Evaluate Transform. It is constructing the sumtransform, then evaluating it to get the actual sum.

Don't repeat yourself

sumtransform is a kind of meta transform; a transform that makes another transform. And this is repeated. Can't we just write it once, and use it twice? Yes we can.

This time, we've wrapped the whole transform in another evaluate transform. We're doing this so we can add st to the scope. st is the old sumtransform. By adding it to the scope, we can use it multiple times.

Then inside the sub-transform (which is wrapped in a quote transform to stop it from being evaluated too early), st is available in the scope. So sumtransform can just select it using a path, and sum itself can evaluate it, again using the path. You'll see the results are the same as in the previous transform.

Let's make something more powerful, an average transform:

This is getting complex!

What we've got is a couple of levels of evaluate transform, successively adding more to the scope.

In the outermost layer, we add st as before. It gives us the basic sum transform.

Then in the next layer, we create an average transform, avgt, in the same manner (constructing a structured list transform out of parts), including st in it.

Let's look at that:

{
  "!": ...,
  "avgt": [
        "&&",
        {":": "&/"},
        {"!": "^@.st"},
        {
          "&": "len",
          "list": "^$"
        }
      ]
}

avgt is a list containing

  • the division operator (builtin), "&/",
  • the sum, using st. Note that if we want the actual sum to be calculated here, we need to evaluate "^@.st" twice; once to evaluate the path and get the st (which is a transform), then a second time to evaluate that transform, and get the actual sum. In the list context it would just be evaluated once. We can force it to be evaluated twice by wrapping it in an evaluate transform (which will be evaluated, forcing the value of "!" to be evaluated, then it will be evaluated again by the evaluate transform).
  • the number of items being summed, which is calculated using the len builtin.

You'll see the evaluated avgt being shown as avgtransform in the result:

"avgtransform": [
    "&/",
    10,
    4
  ]

and of course the result of evaluating this is in avg, which is 2.5 .

The alternate Evaluate Transform

There's another form of Evaluate Transform, which uses a double exclamation mark ("!!"):

{
    "!!": <sub-transform>,
    "s": <scope delta>
}

This is evaluate as follows:

evaluate ( source, transform, scope, library ) 
= evaluate ( source, sub-transform, scope-update ( scope, transform["s"] ), library )

This is a slightly cleaner way of doing evaluate transforms (we don't mix the scope delta and the sub-transform in the same dict), but is a little more verbose. Note that it's useful for higher level transforms (like a higher level function). It can just be a little easier to assemble transforms from parts using this form (eg: see the combine transform in the core library).

Removing factored sub-transforms from the transform

The complex avg transform above was really messy because a couple of utility transforms (st and avgt) were included inside it. Wouldn't it be nice if we could put them somewhere else?

We could put them in the source, and let the user pass them in via the source, like this:

That kind of thing works. It's a bit perverse though; we don't really want the user passing in transforms via the source.

In some cases we might want that. For example, imagine that we provide a map transform which will apply a user supplied transform to every item in a list, and return the list of transformed items? That might be useful! Here's an implementation:

That's one of the great strengths of sUTL; the code is data, and the data is all potentially code. It's a trait that all LISP variants have, and which has been carefully cultivated here.

But for real modularity, you really want to make reusable pieces and put them somewhere accessible for using elsewhere. Not in the transform, not in the source, but in the library.

That's what the next section is all about.

Next: Libraries