So far in out tutorial series, we have been consuming expressions, without really diving deep into them, to see what they’re truly made of. In this tutorial, we will have a really close look at them, break them down, and look at all the different iterators that exists.
First things first; an expression is defined as starting with an @ sign, and ending with either «?value», «?name», «?count», «?node» or «?path». In between its beginning and its end, you can put zero or more «iterators». An iterator returns zero or more nodes, and there’s a whole range of specialized iterators, for traversing your pf.lambda graph objects.
This makes the simplest possible expression you can create, become one of these;
set:@?name set:@?value set:@?node
None of the above expressions have any iterators at all, btw.
If you run the above code through your pf.lambda executor, and look at the results, you will see that the first [set] statement removes the name of the first node, the second removes the value of the second node, and the third removes the node altogether, which is why there’s only two nodes in your result. This is because neither of the above [set] statements have any parameters, and hence they sets their destination to the «null value», which for name means “”, value means null, and node means «remove it all together».
Another thing you will notice, is that all the above expressions act upon the node they’re typed out inside of. This is because there are no «iterators», and all expressions, by default, starts out at the node they’re typed out inside of. The third thing you also might have noticed, is that we did not type out neither an example of «?count», nor an example of «?path». This is because both count and path are «read-only» values, and hence using them as destinations, would result in an execution error. Count and path can be used as «sources», but not as «destinations». Now however, let’s dissect our iterators.
An iterator start out with a slash, and can optionally end with a slash. This means that these two expressions, are synonyms;
BTW,the above code won’t really create a visible result, or actually even execute predictably in the pf.lambda executor, since it deletes the root node entirely. The above iterator is the «root node» iterator, and it basically means; «give me the root node of my execution tree». To understand it conceptually, consider this code;
lambda lambda lambda set:@/..?value source:Hello World
The above code contains a lambda object, containing a lambda object, containing another lambda object, containing a [set] statement. The [set] statement, sets the root node’s value to «Hello World». If you run the above code through the pf.lambda executor, it neither will produce any visible result however. This is because the root node is invisible when we use the pf.lambda executor, since it it a «wrapper node», automatically created for us by the pf.lambda executor. However, the value of that node, even though it is invisible, will become «Hello World» after execution. A more useful example can be created by combining the «root node iterator» with an «indexed child iterator». Consider this code;
lambda lambda lambda set:@/../0?name source:Hello World
The above code will actually create a visible result, as you can see. The first node in our result, should now have changed its name, to «Hello World». This is because our second iterator, extracts the zeroth child of the root node, which of course is, our first lambda node. If we add another iterator, after our first indexed iterator, referencing its first child again, then the result will show that the second [lambda] statement changes its name. For the record, [lambda] is a keyword in pf.lambda, and will be explained in later tutorials.
Each iterator reacts upon the result of its previous iterator this way, and they are hence said to be «left associative».
If an iterator contains an integer number, like the above code, then it is an «indexed child iterator», and the number it contains, defines which child node, of the previous result node(s), we are trying to access.
If an iterator does not find a match, then that iterator will return «no result», and the entire expression will hence be discarded, as having «no value, or yield null». Try to change the second zero to the integer value of «1», and then click execute. Now we have no result, since there is only one child in our second lambda statement, and trying to reference the «1st node», which is actually the «2nd node», won’t produce a result. Hence, the expression as a whole, won’t produce any result.
Another way you could accomplish the same, as when you access the zeroth node, would be to use the «all children iterator». The all children iterator, is declared using a single asterix character; /*. Consider this code;
lambda lambda lambda set:@/../*/*?name source:Hello World
As you can see for yourself, the above code produces the exact same result as our previous example, even though they are semantically very different. The «asterix iterator» yields «all children of results from previous iterator». However, since there is only one child in each node above, the result becomes the same as when we used our «indexed iterator». If you wish to understand the difference between these two types of iterators, consider this code;
lambda _x lambda lambda set:@/../*/*?name source:Hello World
In this piece of code, both the [_x] node, in addition to our second [lambda] node, acquired the name of «Hello World». This is a perfect example of how an expression might yield multiple results. If you change the above asterix iterators, to become indexed iterators, with the value of «0», then only the [_x] node would have its name changed.
Another thing you will notice, is that if you change the first asterix to a «0», and the second to a «1», ending up with this code;
lambda _x lambda lambda set:@/../0/1?name source:Hello World
Then you would actually end up producing a result, contrary to the first time we tried this. This is because the first children of our root node, actually has two children nodes now.
Sometimes you wish to retrieve one or more nodes with a specific name. This is very easy, and can be accomplished using the «named iterator». The named iterator is starting with a slash, like all other iterators are, following the name of the node you wish to retrieve. Consider this code;
lambda _x lambda lambda set:@/../*/*/_x?name source:Hello World
The «named iterator» simply returns the matches from the iterator to the left of itself, matching the name it is looking for. Notice how this differs semantically from both the asterix iterator, and the indexed iterator, that both yields the children of the nodes from the iterator to the left of themselves. Another iterator that works the same way, is the «valued iterator», which is identically to the «named iterator», except it starts out with an equal sign. Consider this code;
lambda _x:success _x:error lambda lambda set:@/../*/*/_x/=success?name source:Hello World
The above code changes only the node’s who’s value is «success», and not the one who’s value is «error». This is because, even though they both match the «named iterator» above, only the first [_x] matches the «valued iterator»‘s value of «success».
This begs the question of what you do, when the name or value, of a node you wish to filter for, is either an integer number, or another special character, such as the equal sign. In such cases, you can actually escape the first character of your iterator, by prepending your iterator’s value, with a back slash. Consider this code;
= 555 * set:@/../*/\=?value source:success 1 set:@/../*/\555?value source:success 2 set:@/../*/\*?value source:success 3
The above piece of code, actually creates a node tree for us, where one of the node’s name is the «equals sign», while another node’s name is the string «555», and a third node’s name is «*». Maybe not the best example of exactly how, and why you’d need to escape your iterators. It however, still does the job, of explaining the concept, I think.
If you have even more complex iterators, you might also need to put your entire iterator inside of a «string literal». For instance, imagine you’re looking for a name containing a carriage-return/line-feed combination. At this point, you’d have to put your entire iterator inside of a «string literal», at which point you could compose carriage return and line feed characters, to match against the name of your node. Consider the following code;
"foo\r\nbar" set:@/../*/"foo\r\nbar"?name source:success
As you can see, string literals, are basically composed the same way, you would compose them in C#. In addition to these iterators, there are many more iterators, but they will however be covered in the next blog post in this series.
Thank you for reading, below is the video tutorial explaining the same concept.