📖
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
  • Extending
  • Replacement
  1. Chapter 4 - Reacting with Behaviors

4.5 Adapting Behaviors

Previous4.4 Expanding PurposeNext5.1 Compositions

Last updated 6 months ago

Overview

Just as contexts can be , so can behaviors, although in more limited ways. Adaptation in behaviors is focused on extending or replacing functionality and it is generally very similar to , with the purpose to either make it more specific in terms of its qualifications/contexts and available operations or change it entirely and prevent the original functionality when new qualifications are met. Doing either can be useful when working with third-party packages or when implementing conditional polymorphic functionality, as described below.

Extending

There have been a number of steps throughout this chapter to make the logging feature capable of handling multiple routes of logging; to output to the console or to one or more files. While that is possible with the current implementation, it would require multiple implementations of Perform Console Logging for each different type of route, despite the over-arching behavioral logic (the contexts and qualifications) generally being the same.

The final needed to fully realize this objective will be discussed later, but the implementation can be updated with behavior adaptation to prepare for that. Specifically, the detail of exactly what a specific Console Log Route does can be extracted from Perform Console Logging and implemented through an extending behavior.

To review, consider the Perform Console Logging behavior as it was last seen:

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.

If the idea of a route is considered as an abstraction, it can be reasoned that the only important concepts are the minimum log level and what to do when a log message should be logged for that specific type of route. The minimum log level is something shared by all routes, so it can be part of any abstraction. What to do cannot be captured directly by a context's abstraction though, as it isn't data, it is functionality, so it is what will be re-implemented in an extending behavior.

To enable the extending behavior, Perform Console Logging can consider any message that it would have logged itself as a Valid Log Message and evaluate it, in anticipation that an extending behavior will add an operation to perform its specific type of logging for any Valid Log Message:

Perform Route 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)]?
    
    Valid Log Message :: Log Message : context;
    
    Initiate Logging :: for {messages} 
            whenever |messages| > 0, 
            foreach message in messages?
                evaluate message as Valid Log Message,
                deactivate message.

An internal context is declared the same as any other context, but it is declared within the behavior, and it is only usable within the behavior and any extending behaviors. Any operation that depends on such a context can only be evaluated from within the behavior, since creating or aliasing any instances of that context is not possible from outside of the behavior.

Now the extending behavior can be implemented to perform the actual logging for a Valid Log Message:

Perform Console Logging [Perform Route Logging] ::
    logging per a unique Console Log Route for shared {*Log Message*}: 
        behavior {route, messages}?
        
    Log :: log a Valid Log Message to the console : <message>?
        evaluate "Log: \(message)" as Console Message.

Perform Console Logging has been re-created as the console-specific functionality that extends the more generalized Perform Route Logging. Most of the declaration is the same, but the identifier now uses the stand-in notation to signify that Perform Console Logging stands-in for (with the purpose of extending) Perform Route Logging. This specifies that if a behavior instance of Perform Route Logging has qualified for this Console Log Route and this {*Log Message*}, then this Perform Console Logging is valid and will add its functionality onto Perform Route Logging.

Every new route context type can have an accompanying Perform "Route Context" Logging similar to Perform Console Logging to handle the log messages in its specific way, while the overall filtering and iteration of those messages is left to the abstraction.

Extending behaviors offers a great way to build upon abstractions or more generalized functionality in a conditional way. An extending behavior can also extend multiple behaviors with and or or clauses within the stand-in notation, which provides a way to supplement multiple abstractions with the same code or to link two ancestor behaviors together with new functionality.

Replacement

Where extending builds upon existing functionality with new functionality, replacement prevents the existing functionality from occurring, usually adding new functionality to take its place. This is useful for branching functionality for similar concepts in a conditional manner or for replacing functionality that is outside of the programmer's direct control.

For this example, consider the logging feature built so far as functionality offered by a third-party package that cannot be altered. It would be nice if logs were printed with the type of log preceding the message, instead of just Log: , as is currently done. Unfortunately, the developer of the package did not implement it as such and does not offer a way to customize the messages. This sets the stage for a replacement to be implemented, such as:

Perform Level Specific Console Logging [Perform Route Logging] ::
    logging per a unique Console Log Route for shared {*Log Message*}: 
        behavior {route, messages}
        replaces Perform Console Logging?
        
    Log :: log a Valid Log Message to the console : <message>.
    
        Log Verbose [Log] :: 
            when message(level) = Log Level (Verbose)?
                evaluate "Verbose: \(message)" as Console Message;
        
        Log Debug [Log] :: 
            when message(Level) = Log Level (Debug)?
                evaluate "Debug: \(message)" as Console Message;
        
        Log Warning [Log] :: 
            when message(Level) = Log Level (Warning)?
                evaluate "Warning: \(message)" as Console Message;
        
        Log Error [Log] :: 
            when message(Level) = Log Level (Error)?
                evaluate "Error: \(message)" as Console Message;
        
        Log Unknown [Log] :: `In case a new level is added, at least log it.`
            default?
                evaluate "Unknown: \(message)" as Console Message.

With this replacement behavior, any time Perform Level Specific Console Logging qualifies alongside a qualifying Perform Console Logging, the Perform Console Logging behavior will be ignored and only a Perform Level Specific Console Logging behavior instance will be created. Its Log operation will be performed the same as the Log of Perform Console Logging, except this new Log operation is an operation hierarchy with operations that will handle each type of Log Level to output the message with a specific prefix.

Just like extending behaviors, a replacement behavior is conditional. If desired, this replacement could have a qualification so that it only replaces Perform Console Logging if some condition is met, perhaps a setting within Console Log Route, or even some other context existing that Perform Console Logging does not specify.

Operations can be replaced as well, with matching syntax and similar use. This can be useful within extending behaviors and for non-behavioral operations.

Perform Console Logging has been renamed to Perform Route Logging to indicate how it should be more generalized to any route (and will be made so ). It now has an internal context, Valid Log Message. Any message that has passed the behavior's filter and can be iterated in Initiate Logging is aliased as a Valid Log Message and evaluated.

There are other ways that this could have been handled. Namely through a mapping of Log Level to String, where the String is the prefix. Then the mapping could be performed and used to prepend the correct prefix without this behavior being concerned about the specifics of the Log Level. That strategy would probably be preferable, either through a mapping accessible through an on the Log Message context or a standalone mapping that is purely responsible for that logic as it relates to Log Level.

adapted
operation hierarchies
concept of abstraction
later
operator