📖
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
  • Collection-Dependent Behaviors
  • Managing the Collection(s)
  • Sorting
  • Filtering
  1. Chapter 4 - Reacting with Behaviors

4.3 Working with Buckets

Previous4.2 From 'When' to 'Whenever'Next4.4 Expanding Purpose

Last updated 6 months ago

Collection-Dependent Behaviors

Some behaviors will need to operate for every instance of a type of context. The created in the last chapter is an example of such a behavior. Up to this point, its declaration looks like:

Perform Logging :: handles logging per a Log Settings for a Log Message : 
    behavior {settings, message}
    when message(level) >= settings(minimum level)?
    
    Log :: for {message} when initialized?
        evaluate "Log: \(message)" as Console Message,
        deactivate message.

As it is currently implemented, there will be an instance of this behavior for every Log Message (assuming there is only one Log Settings in the application). That instance will be destroyed after its initialization operation executes. There isn't anything wrong with this implementation, but it does require a bit more work by the application to continually cycle through new instances of the behavior. It also requires the Log Settings to be maintained by another behavior so it would persist across the destruction of this behavior's instances.

These requirements can be removed by making Perform Logging dependent on the application's collection of Log Message instances rather than a single instance. Doing so can be done by updating the behavior, as:

Perform Logging :: handles logging per a Log Settings for {*Log Message*} : 
    behavior {settings, messages}?
    
    Evaluate Messages :: for {settings, messages}
        whenever |messages| > 0,
        foreach message in messages?
            evaluate message for settings,
            deactivate message;
    
    Log :: log a Log Message to the console : <message> for {settings}
        when message(level) >= settings(minimum level)?
            evaluate "Log: \(message)" as Console Message.

By making the behavior dependent on a collection, an instance of Log Settings on its own will be enough to qualify an instance of the behavior, albeit with an empty Log Message bucket, which is fine for this behavior. Whenever the number of messages in the Log Message bucket is more than 0, as specified by whenever > 0, the Evaluate Messages operation will reactively evaluate, being executed for every message that is now in the bucket.

Within this operation, there is a composition-based evaluation. evaluate message for settings initiates the evaluation of any operations that depend upon a Log Message and a composition that consists only of the Log Settings. This evaluation could extend beyond this behavior, to any other behavior that is working with the same Log Settings instance and has an operation for a single Log Message. It would not evaluate any operations that require any Log Message and any Log Settings, as that is not what is being specified by this evaluation.

The one operation that currently depends on a Log Message for that specific Log Settings instance is the Perform Logging behavior's Log operation, as also shown above. It will qualify if the message's log level meets the setting's minimum log level and will then evaluate the message as a Console Message with Log: prepended, just as logging was accomplished before these changes. Once that is done, Evaluate Messages will deactivate the message, regardless of whether it was logged.

These changes accomplish the same functionality as before, but there no longer needs to be a behavior to maintain the Log Settings between Log Message instances. The single Perform Logging behavior instance created for the Log Settings will persist until Log Settings is deactivated, which also grants an improvement to performance.

Managing the Collection(s)

The above changes are an improvement, but they can go a step further to make the behavior as a whole more focused and explicit in its purpose. This can be accomplished by altering what qualifies for the Log Message bucket.

Operations can use buckets the same as any other input context, and just like behaviors, they can customize the contexts of those buckets into new qualification buckets before using them.

Sorting

A behavior can ensure a bucket is sorted per a mapping. Mappings are a restricted way to make an immediate change to a value, often converting the value into a new instance of a different type in the process. In the case of sorting, the mapping will evaluate two inputs to either a or an Int, which will be used to specify whether the first value should be sorted higher than the second value; False or a negative number meaning the first is less-than the second, Unknown or 0 meaning that they are equal (there is no measured distinction between the two), and True or a positive number meaning that the first is greater-than the second.

While not useful for the purpose of this logging implementation, Perform Logging could be updated to show sorting:

Perform Logging :: handles logging per a Log Settings for {*Log Message*} : 
    behavior {settings, messages}
    where messages is messages sorted by
        [(first, second) => first(level) - second(level)]?
    
    Evaluate Messages :: for {settings, messages}
        whenever |messages| > 0,
        foreach message in messages?
            evaluate message for settings,
            deactivate message;
    
    Log :: log a Log Message to the console : <message> for {settings}
        when message(level) >= settings(minimum level)?
            evaluate "Log: \(message)" as Console Message.

Mappings can be declared, similar to a context, or used in-line, as done here (wrapped in square brackets), and they can play a very important role in structuring the code and specifying the direct manipulation of data. However, they are not a core concept to Contextual Programming, so they are not covered in further detail.

This sorting functionality will occur prior to whenever the behavior will perform a reactive operation, if the messages bucket either has new contexts or has had contexts change their state, thus ensuring that the messages bucket is only sorted right before it is used.

Filtering

Filtering contexts is the second way behaviors can control the contents of their buckets, and it's the method that will be relevant to further improving the Perform Logging implementation. Filtering is accomplished in a fashion similar to sorting, where a mapping (the filter logic) is specified after the keywords filtered by, except this mapping takes only one input and outputs a Bool to indicate whether the input passes the filter. Using a filter, Perform Logging can move the qualification of the Log operation to the behavior's bucket, thus simplifying the overall implementation by condensing its operations:

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

The first change to point out is {*Log Message*} replacing Log Message in the behavior description. This change is what makes the behavior collection dependent. Surrounding a context type with curly braces and stars in Rede, generally as {* "Context Type" *}, defines what is called a "bucket" of those contexts. A bucket is an collection of contexts. Contexts can be accessed sequentially (as shown above by foreach message in messages), by index (e.g., messages(0)), or by . When used in a behavior declaration, by default, all instances of the specified context will be provided to the behavior's bucket. However, that can be customized, as discussed .

There are a couple of ways to customize the contexts of a behavior's bucket(s) as a new (a new bucket in this case). The second of which will be applied to the Perform Logging behavior.

The above adds a qualification to the behavior, where messages is messages sorted by [(first, second) => first(level) - second(level)]. This will ensure messages are sorted by their log levels, in order of the lowest log level to the highest. The keywords to define the sort are sorted by, which will apply the subsequent mapping [(first, second) => first(level) - second(level)] to the bucket, messages. The mapping is subtracting the log levels of the two inputs, which can be done since a Log Level is of an Int. The result will be an Int indicating whether the first should be sorted higher or lower than the second. The result of the sort is assigned to a new qualification bucket that is also called messages, since all mappings and their uses in Rede are performed without .

This makes a couple of assumptions about what will use the Log Message context and when it should be deactivated. These assumptions will be discussed and corrected with the help of an abstraction concept covered .

Now a Log Message will only qualify for Perform Logging, as an element of a new messages qualification bucket, if its log level is higher than the minimum log level specified in the settings. Any time there is at least one such message Perform Logging will loop through all the messages, log them as a Console Message and deactivate them. that the qualification value messages is replacing the original messages, so the Perform Logging behavior will only form a relationship with the remaining contexts of the new bucket. It isn't concerned with any Log Message that doesn't qualify, and such a context will automatically be deactivated if it doesn't qualify for some other behavior. If a Log Message were to be persisted by another behavior and should then later qualify for Perform Logging by changing its level, then that Log Message would be added to messages.

later
context ID
below
logging behavior
an enum
qualification value
Recall