📖
A General Introduction to Contextual Programming
  • A General Introduction to Contextual Programming
  • Chapter 1 - Thinking Contextually
    • 1.1 What is a Paradigm?
    • 1.2 What is Contextual Programming?
  • Chapter 2 - Creating Context
    • 2.1 Organizing Data
    • 2.2 Decorators
    • 2.3 Adaptation
  • Chapter 3 - Evaluating with Operations
    • 3.1 Hello World!
    • 3.2 Expanding on 'When'
    • 3.3 Operation Hierarchies
  • Chapter 4 - Reacting with Behaviors
    • 4.1 Revisiting Hello World!
    • 4.2 From 'When' to 'Whenever'
    • 4.3 Working with Buckets
    • 4.4 Expanding Purpose
    • 4.5 Adapting Behaviors
  • Chapter 5 - Abstracting Evaluations
    • 5.1 Compositions
    • 5.2 Operables
  • Chapter 6 - Abstracting Contexts
    • 6.1 Contracts
    • 6.2 Context Identifiers
  • Chapter 7 - Looking to What's Next
    • 7.1 Final Thoughts
    • 7.2 Additional Resources
Powered by GitBook
On this page
  • The Purpose of Groups
  • Fizz Buzz
  • Getting the Input
  • Performing Fizz Buzz
  • A Deeper Hierarchy
  1. Chapter 3 - Evaluating with Operations

3.3 Operation Hierarchies

Previous3.2 Expanding on 'When'Next4.1 Revisiting Hello World!

Last updated 6 months ago

The Purpose of Groups

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.

Fizz Buzz

A good example of operation hierarchies is a classic programming challenge, .

Getting the Input

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:

Fizz Buzz Input String :: a Console Response used to perform fizz buzz : context.
Fizz Buzz :: an Int representing the fizz buzz number : context.

Then the initial operation:

Run Fizz Buzz :: operation when initialized?
    evaluate "Please enter a number." as Console Message,
    evaluate Console Response as Fizz Buzz Input String.

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:

Evaluate User Input :: evaluates the user's provided Fizz Buzz Input String : 
    operation <input>.

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:

Initiate Fizz Buzz [Evaluate User Input] :: operation
    fizz buzz is input to Fizz Buzz, 
    when fizz buzz > 0?
        evaluate fizz buzz.

Notify of Invalid Input [Evaluate User Input] :: operation
    default?
        evaluate "That input is not valid for Fizz Buzz!" as Console Message.

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.

Those familiar with if, else if, and else, or switches, as often used in other paradigms, may notice that hierarchies replace these logical branching structures and the explicit paths that they define. However, operation hierarchies are more flexible, through adherence to the concepts of contextual programming, and enable paths that are compositional (one operation can have multiple parents), and that can be further extended, altered, or replaced, with little to no changes to the related path and its alternatives.

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.

Performing 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:

Perform Fizz Buzz :: performs Fizz Buzz and outputs results : operation <fizz buzz>
    foreach i in Range [1, fizz buzz].

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.

Divisible by 3 [Perform Fizz Buzz] :: operation 
    when i % 3 = 0?
        evaluate "Fizz" as Console Message.

Otherwise [Perform Fizz Buzz] :: operation 
    default?
        evaluate i to Console Message.

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:

Divisible by 5 [Perform Fizz Buzz] :: operation 
    when i % 5 = 0?
        evaluate "Buzz" as Console Message.

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:

Divisible by 15 [Perform Fizz Buzz] :: operation 
    when i % 15 = 0?
        evaluate "Fizz Buzz" as Console Message.

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:

Perform Fizz Buzz :: performs Fizz Buzz and outputs results : operation <fizz buzz>
    foreach i in Range[1, fizz buzz].

Divisible by 3 [Perform Fizz Buzz] :: operation 
    when i % 3 = 0,
    without Divisible By 15?
        evaluate "Fizz" as Console Message.

Divisible by 5 [Perform Fizz Buzz] :: operation 
    when i % 5 = 0,
    without Divisible By 15?
        evaluate "Buzz" as Console Message.

Divisible by 15 [Perform Fizz Buzz] :: operation 
    when i % 15 = 0?
        evaluate "Fizz Buzz" as Console Message.

Otherwise [Perform Fizz Buzz] :: operation 
    default?
        evaluate i to Console Message.

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.

A Deeper Hierarchy

The above implementation works just fine, but below is an alternative version that emphasizes another layer of child operations:

Perform Fizz Buzz :: performs Fizz Buzz and outputs results : operation <fizz buzz>
    foreach i in Range[1, fizz buzz].

Divisible by 15 [Perform Fizz Buzz] :: operation 
    when i % 15 = 0?
        evaluate "Fizz Buzz" as Console Message.

Not Divisible by 15 [Perform Fizz Buzz] :: operation 
    default.

Divisible by 3 [Not Divisible by 15] :: operation 
    when i % 3 = 0?
        evaluate "Fizz" as Console Message.

Divisible by 5 [Not Divisible by 15] :: operation 
    when i % 5 = 0?
        evaluate "Buzz" as Console Message.

Otherwise [Not Divisible by 15] :: operation
    default?
        evaluate i to Console Message.

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:

Perform Fizz Buzz :: performs Fizz Buzz and outputs results : operation <fizz buzz>
    foreach i in Range[1, fizz buzz].
    
        Divisible by 15 [Perform Fizz Buzz] :: operation 
            when i % 15?
                evaluate "Fizz Buzz" as Console Message.
        
        Not Divisible by 15 [Perform Fizz Buzz] :: operation 
            default.
                
                Divisible by 3 [Not Divisible by 15] :: operation 
                    when i % 3?
                        evaluate "Fizz" as Console Message.
                
                Divisible by 5 [Not Divisible by 15] :: operation 
                    when i % 5?
                        evaluate "Buzz" as Console Message.
                
                Otherwise [Not Divisible by 15] :: operation
                    default?
                        evaluate i to Console Message.

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.

Experienced programmers may consider that there is a lot of code here for relatively simple functionality. That is true. Operation hierarchies are intended for more complex logic flows than what Fizz Buzz requires, which is effectively a mapping from a number to a specific output.

While not covered in this book, since it is focused on Contextual Programming, Rede has features that accommodate simpler logic (e.g., mappings) very succinctly.

depend on one another
share the same contexts
anonymous evaluation