📖
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
  • Assembling Contexts
  • Operable Types
  1. Chapter 5 - Abstracting Evaluations

5.2 Operables

Previous5.1 CompositionsNext6.1 Contracts

Last updated 6 months ago

Assembling Contexts

As briefly noted , contexts can be grouped together to form an operable. Operables are what evaluate actually evaluates to initiate operations. Just like a single context is implicitly cast to a composition for activate and deactivate, a single context is implicitly cast to an operable for evaluate. The grouping of contexts with < > prior to an evaluate is an explicit in-line declaration of an operable for those contexts.

Just as with , operables can be used as declared variables within operations, and they can be provided as inputs to an operation. The from earlier can provide some familiar material to see how this can be done. Here is one version of that completed example:

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

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

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

    Initiate Fizz Buzz [Evaluate User Input] :: operation
        fizz buzz is input as 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.

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
        without Divisible By 15?
            evaluate "Fizz" as Console Message.
    
    Divisible by 5 [Perform Fizz Buzz] :: operation 
        when i % 5
        without Divisible By 15?
            evaluate "Buzz" as Console Message.
    
    Divisible by 15 [Perform Fizz Buzz] :: operation 
        when i % 15?
            evaluate "Fizz Buzz" as Console Message.
    
    Otherwise [Perform Fizz Buzz] :: operation 
        default?
            evaluate i to Console Message.

The Perform Fizz Buzz operation, and more specifically its child operations, results in some string being output to the console for every count up to the input number. This is completely fine for a straightforward Fizz Buzz implementation, but it can be altered to make the result more dynamic by abstracting the console messages away from Perform Fizz Buzz and its children. The first step to do so is to declare operables, in Initiate Fizz Buzz, that will later take the place of any evaluation of a Console Message:

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

    Initiate Fizz Buzz [Evaluate User Input] :: operation
        fizz buzz is input as Fizz Buzz, 
        when fizz buzz > 0?
            on fizz buzz is <"Fizz Buzz" as Console Message>,
            on buzz is <"Buzz" as Console Message>,
            on fizz is <"Fizz" as Console Message>,
            on default is <"Neither Fizz Nor Buzz: " as Console Message>,
            evaluate <fizz buzz, on fizz buzz, on buzz, on fizz, on default>.
    
    Notify of Invalid Input [Evaluate User Input] :: operation
        default?
            evaluate "That input is not valid for Fizz Buzz!" as Console Message.

The new lines, like on fizz buzz is <"Fizz Buzz" as Console Message>, are declaring operable variables. Any of which can be evaluated to produce the same as evaluate <"Fizz Buzz" as Console Message>, which, for a single context, is the same as evaluate "Fizz Buzz" as Console Message. The line to start the Fizz Buzz algorithm has also been changed, now being evaluate <fizz buzz, on fizz buzz, on buzz, on fizz, on default>, where there is an in-line operable declaration to group the original Fizz Buzz context with the new operables.

Perform Fizz Buzz will need to update as well, as right now it wouldn't ever be performed with the new Initiate Fizz Buzz code:

Perform Fizz Buzz :: performs Fizz Buzz, evaluating <Console Message> on fizz buzz, 
    <Console Message> on buzz, <Console Message> on fizz, and 
    <Console Message> otherwise : 
    operation <fizz buzz, on fizz buzz, on buzz, on fizz, on default>
    foreach i in Range[1, fizz buzz].

    Divisible by 3 [Perform Fizz Buzz] :: operation 
        when i % 3
        without Divisible By 15?
            evaluate "Fizz" as Console Message.
    
    Divisible by 5 [Perform Fizz Buzz] :: operation 
        when i % 5
        without Divisible By 15?
            evaluate "Buzz" as Console Message.
    
    Divisible by 15 [Perform Fizz Buzz] :: operation 
        when i % 15?
            evaluate "Fizz Buzz" as Console Message.
    
    Otherwise [Perform Fizz Buzz] :: operation 
        default?
            evaluate i to Console Message.

It will be performed with these changes, but it isn't really using the operables. That can be changed by updating all of its evaluate calls to use the operables instead of its own Console Message contexts:

Neither Fizz Nor Buzz Number :: Int : context.

Perform Fizz Buzz :: performs Fizz Buzz, evaluating <Console Message> on fizz buzz, 
    <Console Message> on buzz, <Console Message> on fizz, and 
    <Console Message> otherwise : 
    operation <fizz buzz, on fizz buzz, on buzz, on fizz, on default>
    foreach i in Range[1, fizz buzz].

    Divisible by 3 [Perform Fizz Buzz] :: operation 
        when i % 3
        without Divisible By 15?
            evaluate on fizz.
    
    Divisible by 5 [Perform Fizz Buzz] :: operation 
        when i % 5
        without Divisible By 15?
            evaluate on buzz.
    
    Divisible by 15 [Perform Fizz Buzz] :: operation 
        when i % 15?
            evaluate on fizz buzz.
    
    Otherwise [Perform Fizz Buzz] :: operation 
        default?
            evaluate on default + i as Neither Fizz Nor Buzz Number.

The default evaluate is different than might have been expected, now being evaluate on default + i as Neither Fizz Nor Buzz Number (a new context declared at the top). This isn't appending i to the operable's Console Message. This is appending i, as a Neither Fizz Nor Buzz Number context to the operable. For this to evaluate to something, there will need to be a new operation that expects a Console Message and a Neither Fizz Nor Buzz Number, like so:

Output Non Fizz Buzz :: output a Console Message with a Neither Fizz Nor Buzz Number:
    operation <preceding message, number>?
         evaluate preceding message + number to String.

The Fizz Buzz example works again with Perform Fizz Buzz and its children no longer concerned about the specific messages that should be output for each case of the algorithm. This abstraction can go a step further though. As currently implemented, Perform Fizz Buzz doesn't really care about what contexts the input operables have, so they could be reduced to the basic Operable type:

Perform Fizz Buzz :: performs Fizz Buzz, evaluating Operable on fizz buzz, 
    Operable on buzz, Operable on fizz, and Operable otherwise : 
    operation <fizz buzz, on fizz buzz, on buzz, on fizz, on default>
    foreach i in Range[1, fizz buzz].

    Divisible by 3 [Perform Fizz Buzz] :: operation 
        when i % 3
        without Divisible By 15?
            evaluate on fizz.
    
    Divisible by 5 [Perform Fizz Buzz] :: operation 
        when i % 5
        without Divisible By 15?
            evaluate on buzz.
    
    Divisible by 15 [Perform Fizz Buzz] :: operation 
        when i % 15?
            evaluate on fizz buzz.
    
    Otherwise [Perform Fizz Buzz] :: operation 
        default?
            evaluate on default + i as Neither Fizz Nor Buzz Number.

Now the input operables can really be anything. Perform Fizz Buzz and its children aren't even aware of whether messages are being output anymore, they are purely focused on performing the algorithm up to the input number. However, they are still assuming there will be an operation that can handle the input on default operable with a Neither Fizz Nor Buzz Number context. That can be addressed in many ways, one of which is covered below.

Operable Types

As with any other type that can be used as a variable, a more specific type can be declared. This would permit abstracting away from <Console Message> as an expected operable type to a different type that can be changed over time, without Perform Fizz Buzz needing to update for it. This imparts the same benefit as using only the basic Operable type, but while giving Perform Fizz Buzz some control over the basics of the operables that it will accept. The first step to do so is to declare those operable types:

On Fizz Buzz :: Console Message : operable.
On Buzz :: Console Message : operable.
On Fizz :: Console Message : operable.
On Default :: Neither Fizz Nor Buzz Number : operable.

Now, the operable instance declarations can be updated:

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

    Initiate Fizz Buzz [Evaluate User Input] :: operation
        fizz buzz is input as Fizz Buzz, 
        when fizz buzz > 0?
            on fizz buzz is On Fizz Buzz ["Fizz Buzz" as Console Message],
            on buzz is On Buzz ["Buzz" as Console Message],
            on fizz is On Fizz ["Fizz" as Console Message],
            on default is On Default 
                [0, "Neither Fizz Nor Buzz: " as Console Message],
            evaluate <fizz buzz, on fizz buzz, on buzz, on fizz, on default>.
    
    Notify of Invalid Input [Evaluate User Input] :: operation
        default?
            evaluate "That input is not valid for Fizz Buzz!" as Console Message.

Notice how On Default is defined as an operable for Neither Fizz Nor Buzz Number, but when being created it is provided an Int, to be cast to the Neither Fizz Nor Buzz Number and a Console Message. Operable types, like compositions, are fulfilled when they have at least the stated contexts of the type, but they can include additional contexts as well, which influence the final evaluation. In this case, on default is an operable that meets the expectations of On Default with an additional Console Message context, which will enable its evaluation to meet the requirements for the Output Non Fizz Buzz operation.

The Perform Fizz Buzz declaration needs updating as well:

Perform Fizz Buzz :: performs Fizz Buzz, evaluating On Fizz Buzz, 
    On Buzz, On Fizz, and On Default as appropriate : 
    operation <fizz buzz, on fizz buzz, on buzz, on fizz, on default>
    foreach i in Range[1, fizz buzz].

    Divisible by 3 [Perform Fizz Buzz] :: operation 
        when i % 3
        without Divisible By 15?
            evaluate on fizz.
    
    Divisible by 5 [Perform Fizz Buzz] :: operation 
        when i % 5
        without Divisible By 15?
            evaluate on buzz.
    
    Divisible by 15 [Perform Fizz Buzz] :: operation 
        when i % 15?
            evaluate on fizz buzz.
    
    Otherwise [Perform Fizz Buzz] :: operation 
        default?
            on default(0) is i as Neither Fizz Nor Buzz Number,
            evaluate on defualt.

The resulting code maintains most of its abstraction, is cleaner looking, and more readable. Perform Fizz Buzz requires its specific operables, but all of them except On Default could change freely. Only On Default is to maintain Neither Fizz Nor Buzz Number as its first context for Otherwise to ultimately update with its own number. As discussed above, the provided on default operable can include any number of other contexts that ultimately determine what operation(s) will be performed upon evaluate on default.

While not explicitly discussed, compositions used as inputs to operations work the same way as operables. There is the basic Composition type for the loosest abstraction and the more restrictive custom types that allow the operation to inject its own contexts, with any number of other contexts possibly being carried along with the input composition to influence the eventual evaluation.

Also similar to , the elements of an operable are still operables. So even though an operation may have one of these declared operables input, it can't get the contexts within it out to reference or change directly. It can alter the operable itself though, such as by changing the contexts for different contexts.

compositions
compositions
before
FizzBuzz example