📖
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
  • Overview
  • Exclusivity
  • Shared
  • Fulfillment
  • Default
  • Optional
  • Relation
  • Coexistent
  1. Chapter 4 - Reacting with Behaviors

4.4 Expanding Purpose

Overview

Behaviors are intended to define various aspects of an application's literal behavior under various contextual circumstances. These circumstances and the functionality of the behavior through its operations define its purpose. Previously, the circumstances were only defined by the contexts themselves and any qualifications that the behavior may have for them, but there are settings for a behavior's contexts that can expand the purpose further.

These settings, described below, can be used individually or in combination to affect a change in how a behavior obtains or manages its contexts. The logging feature that has been worked on throughout this chapter can be expanded upon to better discuss what these settings can do. These changes not only work to better showcase the settings, but they also help realize one of the original intentions of logging, which was that logs could be routed to different outputs, not only the console as has been done so far.

First, there needs to be a context to differentiate console routing from the overall log settings:

Console Log Route :: Log Settings : context.

The Log Settings used so far is fine to define the underlying minimum log level, so the new context Console Log Route can simply be a descendant of that. The Perform Logging behavior should be updated for that as well:

Perform Console Logging :: 
    handles logging per a Console Log Route for {*Log Message*} : 
        behavior {route, messages}
        where messages is messages filtered by
            [(message) => message(level) >= route(minimum level)]?
    
    Log Messages :: for {messages}
        whenever |messages| > 0,
        foreach message in messages?
            evaluate "Log: \(message)" as Console Message,
            deactivate message.

There's not a lot of changes here, just swapping Log Settings for Console Log Route, renaming the behavior to Perform Console Logging, and renaming the behavior's context settings to route. However, this decouples the behavior of console logging from the general Log Settings.

Maintain Console Log Route ::
    maintain the existence of Console Log Route in accordance with App State : 
        behavior {route, app}?
        
    Stop Logging :: for {route, app}
        whenever app is shutting down?
            activate Log Message ["Stopping Logs.", level [Log Level(Debug)] ],
            deactivate route.

Now that the logging functionality requires the Console Log Route context instead of Log Settings, the initial operation also needs to be updated. It may now look like the following:

Hello Log :: when initialized?
    activate app is App State,
    activate Console Log Route,
    activate Log Message ["Hello Log!", level [Log Level(Debug)] ],
    activate Log Message ["A verbose log.", level [Log Level(Verbose)] ],
    activate Log Message ["About to shut down!", level [Log Level(Warning)] ],
    app(continue running) is False.

These changes set the stage to move away from logging being console specific.

Exclusivity

The next step to enable multiple log routes pertains to the concept of the contexts' exclusivity, which defines how contexts might be shared between behaviors. The term "shared" itself is actually a keyword, shared, to specify exclusivity, with its counterpart unique. By default, all behavior contexts are unique per instance of the same type of behavior.

In other words, if there were two Console Log Route contexts, there would be two Perform Console Logging behavior instances and any Log Message contexts that qualify for both would be split between them. Each Console Log Route would be unique to their instance of the Perform Console Logging behavior and each Log Message would be unique to only one of those instances.

Shared

To address this issue, shared can be specified for {*Log Message*}, like so:

Perform Console Logging :: 
    handles logging per a Console Log Route for shared {*Log Message*} : 
        behavior {route, messages}
        where messages is messages filtered by
            [(message) => message(level) >= route(minimum level)]?
    
    Log Messages :: for {messages}
        whenever |messages| > 0,
        foreach message in messages?
            evaluate "Log: \(message)" as Console Message,
            \deactivate message.

If a developer wants to be explicit, then unique can also be specified for Console Log Route without any change to this current functionality:

Perform Console Logging :: 
    handles logging per a unique Console Log Route for shared {*Log Message*} : 
        behavior {route, messages}
        where messages is messages filtered by
            [(message) => message(level) >= route(minimum level)]?
    
    Log Messages :: for {messages}
        whenever |messages| > 0,
        foreach message in messages?
            evaluate "Log: \(message)" as Console Message,
            \deactivate message.

shared should also be applied to the Maintain Console Log Route as well, so that App State could be shared across multiple route instances:

Maintain Console Log Route ::
    maintain the existence of Console Log Route in accordance with shared App State :
        behavior {route, app}?
        
    Stop Logging :: for {route, app}
        whenever app is shutting down?
            activate Log Message ["Stopping Logs.", level [Log Level(Debug)] ],
            deactivate route.

The main consideration to be made when sharing contexts is what purpose multiple instances of the shared context will serve for the affected behavior. Are there qualifications that will still distribute the different instances of a shared context across behavior instances or is it expected for there to really only be one such context in the application? Both are valid scenarios for different purposes of a behavior.

Fulfillment

While the logging feature has been getting a number of improvements, the overall flow of the application has been built to support it without much attention. Most notably, the initial operation of the application is kind of clunky and hasn't been improved for a while. Recall that it currently looks like the following:

Hello Log :: when initialized?
    activate app is App State,
    activate Console Log Route,
    activate Log Message ["Hello Log!", level [Log Level(Debug)] ],
    activate Log Message ["A verbose log.", level [Log Level(Verbose)] ],
    activate Log Message ["About to shut down!", level [Log Level(Warning)] ],
    app(continue running) is False.

Granted, all the Log Message contexts that are activated would probably occur throughout other parts of the application, as would setting continue running to False, but a string of activations for long-running contexts, currently just App State and Console Log Route would only get longer as other routes are added. Behaviors can inherently take on the responsibility of those initializations through fulfillment settings.

Fulfillment pertains to how a behavior's contexts are fulfilled. By default, a behavior requires its contexts to be activated somewhere else in the application, and only when all of its required contexts are activated and available at the same time will a new behavior instance be created for them. This default setting is the existing fulfillment. The Maintain App State behavior can include the existing keyword to make it explicit that the App State must be activated elsewhere, although no functionality of the behavior will change by doing so:

Maintain App State :: maintain the existence of existing App State : 
    behavior {app}?
    
    Cleanup App State :: for {app}
        whenever app is shutting down,
        after all?
            deactivate app.

Default

Another fulfillment setting is default, which declares that the following context will be created with its default state as long as all other contexts of the behavior have been fulfilled. If there are no required contexts (contexts that must be fulfilled as existing), then one instance of the behavior will automatically be created when the application starts with all of its default contexts. This works great for Maintain App State, which can be changed to:

Maintain App State :: maintain the existence of default App State : 
    behavior {app}?
    
    Cleanup App State :: for {app}
        whenever app is shutting down,
        after all?
            deactivate app.

Since this behavior will be created when the app starts, it can also serve as a new entry point to the application. To embrace that responsibility, it can be refactored to include the initial operation:

Run App :: create the default App State, initialize and run until shutdown :
    behavior {app}?
    
    Initialize :: for {app} when initialized?
        activate Console Log Route,
        activate Log Message ["Hello Log!", level [Log Level(Debug)] ],
        activate Log Message ["A verbose log.", level [Log Level(Verbose)] ],
        activate Log Message ["About to shut down!", level [Log Level(Warning)] ],
        app(continue running) is False;
    
    Shutdown :: for {app}
        whenever app is shutting down,
        after all?
            deactivate app.

The initialization operation here is kind of messy still, and it's peculiar for it to shut itself down during initialization. It can be simplified to one start up message and the trigger to shutdown can be tied to user input instead of assumed:

Run App :: create the default App State, initialize and run until shutdown :
    behavior {app}?
    
    Initialize :: for {app} when initialized?
        activate Console Log Route,
        activate Log Message ["App Initialized.", Level [Log Level (Debug)] ];
    
    Run :: for {app} whenever app(continue running)?
        activate Log Message ["App is running.", Level [Log Level (Verbose)] ],
        evaluate Console Response,
        activate Log Message ["About to shut down!", Level [Log Level (Warning)] ],
        app(continue running) is False;
    
    Shutdown :: for {app}
        whenever app is shutting down,
        after all?
            deactivate app.

Now the application will initialize itself with an App State and then activate a default Console Log Route before logging its first message. It will then perform Run, since app(continue running) is True by default, which will log another message and then wait until the user provides any input to the console. After input is received, Run will log another message and set its own qualification condition, app(continue running), to False which will subsequently initiate Shutdown to be performed to deactivate the App State, effectively stopping the application.

Optional

This is looking much better, and it sets the application up to have an input loop that can be used for the user to actually use the application. There's another small improvement that can be made to the application by using a third fulfillment setting. That setting is optional, which declares that a context will be fulfilled by an existing context, when it is available, and until then it will be fulfilled by a default context if all of its other contexts are fulfilled.

The optional keyword can be added to the Maintain Console Log Route behavior declaration to make the Console Log Route optional, like so:

Maintain Console Log Route ::
    maintain the existence of an optional Console Log Route in accordance 
        with shared App State : behavior {route, app}?
        
    Stop Logging :: for {route, app}
        whenever app is shutting down?
            activate Log Message ["Stopping Logs.", level [Log Level(Debug)] ],
            deactivate route.

With this change, Maintain Console Log Route will create a default Console Log Route once an App State is made available, if there is not already a Console Log Route for it to choose from. Note that even though App State is shared a default Console Log Route will not be created in a second instance if a different Console Log Route is already available, unless there is a second App State that does not have an accompanying existing Console Log Route.

Since Run is already making a default Console Log Route, that specific line can be removed, making its Initialize operation simpler as just:

Initialize :: for {app} when initialized?
    activate Log Message ["App Initialized.", Level [Log Level (Debug)] ];

Once the application starts, it will create an App State and see that doing so also fulfills the Maintain Console Log Route behavior's required contexts, leading to the creation of a default Console Log Route. This then leads to a few scenarios that can occur:

  • If App State were to make a Console Log Route in its Initialize, then that new context instance would automatically take the place of that default Console Log Route for all behaviors in which it has qualified, essentially overriding it.

  • If an overriding Console Log Route were to be deactivated at some point, then a new default instance would be created to maintain the existence of the behavior for the persisting App State.

  • If App State were to be deactivated, then any default Console Log Route would continue to persist (if it qualifies for another behavior) as if it had been activated on its own elsewhere in the application.

These rules lead to optional contexts behaving consistently with others, but with the added feature of being overridden and replaced to maintain their behavior's functionality. However, per these rules, the lifecycle and functionality of Maintain Console Log Route is altered, specifically in that deactivate route would deactivate the current default route, only to potentially have it replaced by another (were App State to persist long enough for that to occur).

Since this behavior itself should depend on App State(continue running), it can qualify itself on that condition and force its (potentially default) route to be deactivated when it will no longer qualify:

Maintain Console Log Route ::
    maintain the existence of an optional Console Log Route in accordance 
        with shared App State : behavior {route, app}
            when app(continue running)?
        
    Stop Logging :: for {route} when terminated?
        activate Log Message ["Stopping Logs.", level [Log Level(Debug)] ],
        deactivate route.

Relation

The last setting is for specifying the relation of a context to the existence of the behavior itself. Generally, this only matters in terms of how a context may persist upon the destruction of the behavior, as occurs when one of its required contexts no longer qualify. The default setting of a context is for it to be independent, to (attempt to) persist after a behavior has been destroyed by being maintained by another behavior. In terms of the logging feature, if not for Maintain Console Log Route deactivating the Console Log Route when the application is shutting down, the Console Log Route would persist even though App State was deactivated.

Coexistent

However, there is a clear dependency of Console Log Route on the existence of App State, and in particular the existence of Maintain Console Log Route, which would be destroyed if App State were deactivated. The coexistent keyword can declare a context as only continuing to exist as long as all of its specified coexistent behaviors continue to exist. This setting can be applied to the Maintain Console Log Route like so:

Maintain Console Log Route ::
    maintain the existence of an optional coexistent Console Log Route in accordance 
        with shared App State : behavior {route, app}
            when app(continue running)?
        
    Stop Logging :: for {route} when terminated?
        activate Log Message ["Stopping Logs.", level [Log Level(Debug)] ],
        deactivate route.

Now when App State is deactivated, and the Maintain Console Log Route behavior is destroyed, the Console Log Route context will automatically be deactivated, which will also result in Perform Console Logging being destroyed. This means the Stop Logging operation can be reduced to a logging operation, to simply give feedback on what is occurring during a shutdown, whether by App State being prematurely deactivated or the app going through the expected shutdown process:

Maintain Console Log Route ::
    maintain the existence of an optional coexistent Console Log Route in accordance 
        with shared App State : behavior {route, app}
            when app(continue running)?
        
    Log Stop :: when terminated?
            activate Log Message ["Stopping Logs.", level [Log Level(Debug)] ].
Previous4.3 Working with BucketsNext4.5 Adapting Behaviors

Last updated 6 months ago

Context descendants do not inherit the behaviors of their ancestors, so the Maintain Log Settings, , will need to be updated similarly:

This creates a problem for scaling the logging feature and for abstracting this behavior later. In consideration of the former, if there were a File Log Route, it would be reasonable to support multiple instances of such a route, perhaps so some messages were written to one file, or another file, or both. Using the default exclusivity, there would be no support for the same messages being written to both files. In consideration of the latter, if Console Log Route were and this behavior were made to be generalized across multiple types of routes, then a message could not be logged for both the console and file.

from earlier
to be abstracted