Prerequisites: You should have read Text Generation Part Two.
The first two parts of this tutorial covered basic text generation. We are now ready create some more advanced phrases.
The .tag()
suffix allows us to attach a name to a phrase. We first encountered tags in part one, where they were used to tag phrases for data population. However, there are other uses for tagged phrases as well. Sub-phrases that are tagged with a name can be reused in a containing phrase and also provides access to additional properties for the sub-phrase. Examine the JavaScript in listing 1 below.
Tagged phrases are available for reuse via the .tags
prefix.
The first sub-phrase of example1
is tagged as pet
In the second sub-phrase, _.a.tags.pet
is used to refer to the sub-phrase tagged as pet
. The .tags
prefix provides access to the collection of tagged phrases and it must be followed by the tag name associated with sub-phrase. The .a
prefix adds the appropriate indefinite article to the sub-phrase. During text generation a new (and different) animal will be picked at random for each of the two sub-phrases.
Sometimes when referring to a tagged phrase, we don't want to generate entirely new text. We just want repeat the same text that was already generated in an earlier sub-phrase. The third expression in example1
is an arrow function. Whenever a function is encountered during text generation, the function is called with the tags collection as its argument, which gives the function access to the tagged phrases. In this case the text property of the pet
sub-phrase is returned.
Study example2
. The pet
tag refers to the whole phrase, _.a.pick()
, which first picks an item and then adds an indefinite article. When the pet
phrase is populated, the data needs to go to the inner .pick
phrase, not the outer .a
phrase, but the tag refers to the phrase as a whole with both prefixes applied. This is not a cause for concern, however, because .populate()
will always identify the innermost (terminal) phrase of a tagged phrase and deposit the data there.
We can reference the data associated with the phrase by using the .data
property. Here, tags=>pet.data.problem
returns the problem associated with the selected pet and tags=>tags.pet.data.value
returns the primary data associated with the pet
phrase, in this case the selected animal with an indefinite article prepended. If .pick
selects an octopus then .data.value
contains an octopus
, but .data.animal
contains the unaltered data, octopus
. At the time of data population, if the incoming data object does not explicitly have a .value
property, it will be created for the phrase and set to the first property of the incoming data object.
We can also reference the inner .pick
phrase of the pet
phrase in example2
by using the inner
suffix: tags=>tags.pet.inner.data.value
. We can chain as many .inner
properties as needed to reach a deeply nested phrase.
Alternatively, we can use parentheses to nest phrases and create tags at different levels of nesting. This is shown in example3
Data population for standard tagged phrases expects the nesting of the sub-phrases to align with the structure of the incoming data. Recall that when .populate
is called with an Object as an argument, the property keys of the Object are matched against the ID tags of the sub-phrases and the data contained in each matching property of the Object is sent to populate the corresponding sub-phrase. If the sub-phrase has its own tagged sub-phrases, the data object is further destructured and the corresponding sub-properties are applied to the sub-sub-phrases. This process of destructuring and applying continues to the innermost depths of the phrase.
With standard tags, if the data you are working with has a structure that doesn't align very well the phrase's structure, the data will not be matched properly. Of course, you can remap the data to match the phrase, but sometimes it's easier to give the phrase the responsibility of parceling out the data to the sub-phrases. This is done by creating a cache tag for the phrase. Like standard tags, cache tags provide a means of referring to data associated with the phrase, but unlike standard tags, the data is not automatically propagated. Instead, arrow functions are used in the phrase's expressions to extract the phrase's data.
Refer to example4
above. The data, {available:{animal:"cat"}}
, assigned to the phrase via .populate
, is accessed through the pet
tag using arrow functions.
Some prefixes expose additional metadata. The .index
and .total
metadata are available for .cycle
, .favor
, .first
, .last
, .pick
, .roll
, and .series
. The .total
property is the length of a phrase's array. The .index
property is the index number of the selected sub-phrase from the phrase's array and is zero based. Listing 2 shows examples of using metadata.
The .series
property requires some special handling when accessing metadata. Once the items in the series are depleted, the series returns an empty array for the results and there are no items of metadata to access. Attempting to access non-existent properties will cause an error. Therefore, we can only access the metadata for .series
under certain conditions. Examine example2
in the listing above. The .also
suffix signals the start of another ishml.Phrase
that will only be evaluated and appended if the preceding phrase returned at least one result. If the preceding series is depleted, the phrase associated with .also
will be skipped. Like .then
, it is acceptable to chain prefixes after .also
.
The .per
suffix repeats a phrase for as many entries as there are in the referenced tagged phrase. Use it whenever you want to iterated through the array of sub-phrases. See listing 3 below.
The .concur
suffix filters a set of sub-phrases so that it conforms to the criteria of a provided rule. See listing 4 below.
The example1
phrase picks a hobby for each person based on their interest. To filter the possible hobbies by those that match the person's interest, we add the .concur
suffix to the hobby
sub-phrase. The rule function returns true if the hobby matches the person's interest or false if it does not. This function receives the tags collection as its first parameter and the result of each sub-phrase evalution of the targeted phrase as the second parameter. Sub-phrases that do not meet the criteria are removed from consideration before .pick
is applied.
Continue to part four to learn about recursive phrases.