Contexts
Why Contexts?
Contexts are an interesting approach to achieve dynamic behavior of objects. Let's think of a program, controling a robot. A robot may act differently when a human is nearby or not. Another example is an energy system that should behave differently on a sunny summer day than on a cloudy and snowy winter day. The context, in which such a program runs could be defined as sunnyDay
, warmWeather
or humanNearby
. It describes the properties of the environment or situation in which the software runs.
Context Definition in Contexts.jl
In Contexts.jl
, Context
is an abstract type.
Contexts.Context
— TypeContext <: AbstractContext
Abstract supertype for all concrete context types.
By defining a new Context, a new struct <ContextName>ContextType
will be created as a subtype of Context
:
struct <ContextName>ContextType <: Context end
and then, a singleton object of this type is defined:
<ContextName> = <ContextName>ContextType()
In that way, contexts are available as arguments of methods to be utilized by the multiple dispatch.
Context Usage
Defining new Contexts with @newContext Macro
To create a new context, use the @newContext
macro:
@newContext Context1
Contexts.@newContext
— Macro@newContext(contextName)
Macro to define a new context type and singleton instance. Accepts a String
, Symbol
, or an expression of multiple names. Creates a struct subtype of Context
and registers it.
Example: @newContext("MyContext") @newContext(AnotherContext) @newContext FirstContext, SecondContext
Context-dependent Behavior with the @context Macro
Since Context1
is now available as a variable representing a value of the context-specific type Context1ContextType
, it can be used in function arguments and using the multiple dispatch, context-dependent behavior is realized. For easier Syntax, the macro @context
is defined. Depending on its usage, it fulfills different tasks.
Calling @context
on a function definition will define a context depening function, by adding an attribute of type Context1ContextType
as the first function argument:
@context Context1 function HelloWorld()
println("Hello World in Context 1!")
end
@context Context2 function HelloWorld()
println("Hello World in Context 2!")
end
Calling @context
on a function (or macro) call will add the context as the first attribute.
@context Context1 HelloWorld()
will therefore print Hello World in Context 1!
and
@context Context2 HelloWorld()
will print Hello World in Context 2!
. This also means that executing HelloWorld(Context1)
would also print Hello World in Context 1!
. Also the variable context
is available in functions.
Contexts.@context
— Macro@context(cname, expr)
Macro to inject context information into a function or macro call or function definition. Adds context type or name as an argument to the function or macro.
A generic context type can be specified with Any
.
Arguments:
cname
: Context name or typeexpr
: Function, macro call, or function definition
Example: @context MyContext function myfunc(x::myType) ... end @context Any function myfunc(x::myType) ... end @context MyContext myfunc(x)
It is also possible to use a tuple of multiple contexts:
@context (Context1, Context2) HelloWorld()
Activeness of Contexts
When programming with contexts, you might want to define all possible contexts at the beginning, but only some of them are active at the same time. Therefore the contexts in Contexts.jl can be activated and deactivated by
Contexts.activateContext
— FunctionactivateContext(context::T) where {T <: Context}
Activates a context and runs compiled PetriNet.
Contexts.deactivateContext
— FunctiondeactivateContext(context::T) where {T <: Context}
Deactivates a context and runs compiled PetriNet.
After the definition of a context, the context will be deactive. When a clean start is needed, the function
deactivateAllContexts()
can help.
Note, that functions can of course also run when a deactivated context is handed as a function argument. The activeness of a context can be checked with:
Contexts.isActive
— MethodisActive(context::T) where {T <: Context}
Checks if a context is currently active. Returns true
if active, false
otherwise.
This can be used in if
-statements to avoid calling functions with deactivated contexts or to have varying functionality depending on activeness.
@context Context1 function printContext()
isActive(context) ? println("Context is active") : println("Context is not active")
end
The macro @activeContext
only calls the function if the context is active:
Contexts.@activeContext
— Macro@activeContext(cname, expr)
Macro to conditionally execute a function or macro call only if the context is active. Wraps the call in an if isActive(context) block.
Arguments:
cname
: Context name or typeexpr
: Function or macro call
Example: @activeContext(MyContext, myfunc(x))
A more conventient way to work with contexts and context-specific functions is via context groups and state machines, see ContextGroup
and ContextStateMachine
.
As explained in Context Modeling, activating contexts will run Petri nets that check the compliance of the contexts' activeness with constraints. However, it is also possible to (de-)activate contexts without the Petri net. This can be helpful to reach valid initial states.
Contexts.activateContextWithoutPN
— FunctionactivateContextWithoutPN(context::T) where {T <: Context}
Activates a context without running PetriNet logic. Mainly used for internal. Should be used with caution
Contexts.deactivateContextWithoutPN
— FunctiondeactivateContextWithoutPN(context::T) where {T <: Context}
Deactivates a context without running PetriNet logic. Mainly used for internal. Should be used with caution
(de)activating contexts without Petri nets can lead to reaching invalid states that are not correctly healed after running the PN.
Boolean Expressions on Contexts
The |
, &
and !
operators can be used to create Boolean expressions on Contexts.
Contexts.OrContextRule
— TypeOrContextRule <: AbstractContextRule
Represents a logical OR between two contexts or context rules.
Fields:
c1
,c2
: Contexts or context rules.
Example: OrContextRule(ctx1, ctx2)
Contexts.AndContextRule
— TypeAndContextRule <: AbstractContextRule
Represents a logical AND between two contexts or context rules.
Fields:
c1
,c2
: Contexts or context rules.
Example: AndContextRule(ctx1, ctx2)
Contexts.NotContextRule
— TypeNotContextRule <: AbstractContextRule
Represents a logical NOT of a context or context rule.
Fields:
c
: Context or context rule.
Example: NotContextRule(ctx)
Base.:|
— MethodBase.:|(c1::CT1, c2::CT2) where {CT1, CT2 <: Union{AbstractContext}}
Creates an OrContextRule from two contexts or context rules using the | operator.
Arguments:
c1
,c2
: Contexts or context rules
Returns an OrContextRule.
Example: ctx1 | ctx2
Base.:&
— MethodBase.:&(c1::CT1, c2::CT2) where {CT1, CT2 <: Union{AbstractContext}}
Creates an AndContextRule from two contexts or context rules using the & operator.
Arguments:
c1
,c2
: Contexts or context rules
Returns an AndContextRule.
Example: ctx1 & ctx2
Base.:!
— MethodBase.:!(c::CT) where {CT <: Union{AbstractContext}}
Creates a NotContextRule from a context or context rule using the ! operator.
Arguments:
c
: Context or context rule
Returns a NotContextRule.
Example: !ctx
Those expressions can be checked for activeness.
Contexts.isActive
— MethodisActive(contextRule::T) where {T <: AbstractContextRule}
Evaluates whether a context rule is active. Supports And, Or, and Not context rules.
Arguments:
contextRule
: An AbstractContextRule (And, Or, Not)
Returns true
if the rule is active, false
otherwise.
Example: isActive(C1 & C2)
isActive(C1 & (C2 | !C3)) # returns true or false