3.3 Operation Hierarchies
Last updated
Last updated
In some circumstances, as seen in the previous example, operations may or with a similar intent. It can be helpful to organize such operations into a hierarchy, where the shared aspects of the related operations are abstracted to a simplified operation that the other operations are then built upon.
By defining an operation hierarchy, the contexts of the can be shared among their while also requiring new contexts specific to themselves. Similarly, overarching qualifications can be shared through the parent operations, with more specific qualifications defined in the child operations. There is also a unique qualification that can designate one of the operations in the hierarchy to be performed in the case that none of the other operations qualify.
A good example of operation hierarchies is a classic programming challenge, .
Some of the concepts from last section's input validation will be useful in this program, so there will be some parallels in contexts and some operations. To start, the input-related contexts should be declared:
Then the initial operation:
Note that the last line, evaluate Console Response as Fizz Buzz Input String.
, where an unassigned Console Response
is being evaluated (as an ), with the result being converted to a Fizz Buzz Input String
that is also being evaluated without an assignment. Anonymous evaluations can be chained in through casts and conditionals.
Next, the Fizz Buzz Input String
can be handled. It needs to be evaluated as a number, with the result either being a Fizz Buzz
evaluation or notification to the user. Both of these operation paths, the handling of a proper input and the notification for an invalid input, can be abstracted as a parent operation:
The parent operation in this case is simply an operation that takes the expected context but doesn't really do anything with it. The child operations, one for each of the aforementioned paths, can now be implemented:
These operations look much like any other operation, except they are using a context from their parent operation, input
, which they define with [Evaluate User Input]
in the name. This is using the stand-in notation again, here being used to say that these operations will stand-in for the Evaluate User Input
operation, when they qualify. However, these operations do not replace Evaluate User Input
, as any functionality that was defined by Evaluate User Input
would still occur (there is none in this case) before the functionality of these child operations.
The use of default
as a qualification specifies that Notify of Invalid Input
will occur if no other Evaluate User Input
stand-in (child operation) will be performed when both the Evaluate User Input
operation itself and the default operation would have otherwise qualified.
These two operations provide input handling, with proper notification to the user for invalid input, and set the stage to perform the actual fizzing and buzzing of the program, per the evaluate fizz buzz
line of Initiate Fizz Buzz
.
With the validated input aliased as Fizz Buzz
, the core part of the program can be implemented. This can be done in a few steps, the first is to define the parent operation:
This operation group requires the Fizz Buzz
context, but there is a new concept in its declaration. The line foreach i in Range [1, fizz buzz]
shows a qualifier, the foreach
qualifier. This defines the operation as occurring for every value (called i
in this case) for a specific range or collection. The range in this case is an inline declaration Range [1, fizz buzz]
which creates a value for a range of 1 to the value of fizz buzz
, including both 1 and the maximum value.
With the operation being performed multiple times for the number 1 to fizz buzz
, it's up to the child operations to qualify appropriately and output either the current number, "Fizz", "Buzz", or "Fizz Buzz" on each iteration. A good starting point is to focus on handling when the number is divisible by 3, and when it isn't.
Now whenever Perform Fizz Buzz
qualifies, and for each subsequent execution for the foreach
control flow, one of these two child operations will be performed. The first qualifies whenever i
is divisible by 3, and it outputs "Fizz" to the console. Whenever the first doesn't qualify, the default operation will run, which will output i
to the console, after converting the number to a Console Message
.
Handling when i
is divisible by 5 is easy enough with another operation:
This leaves one last case, when i
is divisible by both 3 and 5. Whenever this occurs, i
is also divisible by 15, so one of the simplest and more explicit ways to handle this situation is with another operation:
There's a problem with this code though. As is, whenever i
is divisible by 15, it will output "Fizz Buzz", but then it will proceed to (or be preceded by, as the order is not specified) output "Buzz" and "Fizz". Operations within an operation hierarchy are not naturally exclusive to one another being performed; only the default operation will exclude itself upon the performance of at least one other operation.
To amend this issue, another Relational Control Flow qualifier can be used to specify that Divisible by 5
and Divisible by 3
should not qualify if Divisible by 15
does. This new qualifier is the qualifier, which works similarly to the after
qualifier. Here is the final version of this complete operation hierarchy, using without
:
With that, the Fizz Buzz program now works as expected. It asks the user for a number, validates it, then iterates from 1 to that number, outputting what is expected for each number.
The above implementation works just fine, but below is an alternative version that emphasizes another layer of child operations:
Essentially, this version nests Divisible by 3
and Divisible by 5
within a default operation, Not Divisible by 15
for whenever Divisible by 15
does not qualify. It is, otherwise, functionally the same Fizz Buzz as before. This removes the explicit without
since it is already known that the number is not divisible by 15, but adds an additional layer of operations, with a new default operation.
With explicit, straightforward, hierarchies like these, it can be helpful to leverage Rede's flexible whitespace to express the relationships between the operations, like so:
The above is just one more way that the code can be structured with operation hierarchies. Just as with other logical branching structures, there are many other ways as well, particularly through use of the various control flow qualifiers.