1 Introduction

Today there is a growing trend in having software systems able to operate every time and everywhere, and applications are working side by side, either in a cooperative or in a competitive way. Ubiquitous and pervasive computing scenarios are typical of the Internet of Things (IoT), a cyber-physical communication infrastructure, made of a wide variety of interconnected and possibly mobile devices. As a consequence, modern software systems have to cope with changing operational environments, i.e. their context. At the same time, they must never compromise their intended behaviour and their non-functional requirements, typically security or quality of service. Thus, programming languages need effective mechanisms to become context-aware, so as to detect the changes in the context where the application is plugged in, and to properly adapt to them, with little or no user involvement. Accordingly these mechanisms must maintain the functional and non-functional properties of applications after the adaptation steps. For example, suppose you want to have just a quick look at your mail and at your social platforms when in a hotel: you would like to connect in a secure way, but without bothering with all the details of the wireless connection, ideally in a fully transparent manner.

The context is crucial for adaptive software and typically it includes different kinds of computationally accessible information coming from both outside (e.g. sensor values, available devices, and code libraries offered by the environment), and from inside the application boundaries (e.g. its private resources, user profiles, etc.). The literature proposes many different programming languages that support dynamic adjustments and tuning of programs, e.g. [17, 18, 20, 24,25,26] (a detailed discussion on the great deal of work in this area is in [13, 23]). In this field, Context Oriented Programming (COP) [1, 9, 15, 16] offers a neat separation between the working environment and the application. Indeed, the COP linguistic paradigm explicitly deals with contexts, by providing programming adaptation mechanisms to support dynamic changes of behaviour, in reaction to changes in the context (see [2, 23] for an overview). In this paradigm, programming adaptation is specified using behavioural variations, chunks of code that can be automatically selected depending on the current context hosting the application, dynamically modifying its execution.

To address adaptivity we defined \(\text {ML}_\text {CoDa}\) [3, 10, 11, 13], a core of ML with COP features. It has two tightly integrated components: a declarative constituent for programming the context and a functional one for computing. The bipartition reflects the separation of concerns between the specific abstractions for describing contexts and those used for programming applications [22]. The context in \(\text {ML}_\text {CoDa}\) is a knowledge base implemented as a (stratified, with negation) Datalog program [19, 21]. Applications inspect the contents of a context by simply querying it, in spite of the possibly complex deductions required. The behavioural variations of \(\text {ML}_\text {CoDa}\) are a sort of pattern matching with Datalog goals as selectors. They are a first class, higher-order construct that can then be referred to by identifiers, and used as parameters in functions. This fosters dynamic, compositional adaptation patterns, as well as reusable, modular code. The selection of a goal is done by the dispatching mechanism that inspects the actual context and makes the right choices. Note that the choice depends on both the application code and the “open” context, unknown at development time. If no alternative is viable then a functional failure occurs, as the application cannot adapt to the current context. Non-functional failures are also possible, when the application does not meet some requirements, e.g. about quality of service or security.

The execution model of \(\text {ML}_\text {CoDa}\) assumes that the context is the interface between each application it hosts and the system running it. Applications interact with the system using a predefined set of APIs that provide handles to resources and operations on them. Also, they interact with each other via the context. The system and the applications do not trust each other, and may act maliciously, e.g. one application can alter some parts of the context so driving another in an unsafe state. The application designer would like to detect both functional and non-functional failures as early as possible, and for that \(\text {ML}_\text {CoDa}\) has a two-phase static analysis, one at compile and one at load-time [4, 11, 13, 14], briefly summarised below. The static analysis takes care of failures in adaptation to the current context (functional failures), dealing with the fact that applications operate in an “open” environment. Indeed, the actual value and even the presence of some elements in the current context are only known when the application is linked with it at run time. The first phase of our static analysis is based on a type and effect system that, at compile time, computes a safe over-approximation of the application behaviour, namely an effect. Then the effect is used at load time to verify that the resources required by the application are available in the actual context, and in its future modifications. To do that, the effect of the application is suitably combined with the effect of the APIs provided by the context that are computed by the same type and effect system. If an application passes this analysis, then no functional failure can arise at run time. The results of the static analysis also drive an instrumentation of the original code, so as to monitor its execution and block dangerous activities [4].

In addition to the formal aspects of \(\text {ML}_\text {CoDa}\), a main feature of our approach is that a single and fairly small set of constructs is sufficient enough for becoming a practical programming language, as shown in [7]. \(\text {ML}_\text {CoDa}\) can easily be embedded in a real programming eco-system as .NET, so preserving compatibility with future extensions and with legacy code developed within this framework. Being part of a well supported programming environment minimises the learning cost and lowers the complexity of deploying and maintaining applications. In [7] a prototypical implementation of \(\text {ML}_\text {CoDa}\) is presented as an extension of the (ML family) functional language F#. Indeed, no modifications at all were needed to the available compiler and to its runtime. The F# metaprogramming facilities are exploited, such as code introspection, quotation and reflection, as well as all the features provided by .NET, including a vast collection of libraries and modules. In particular, we used the Just-In-Time mechanism for compiling to native code. As a consequence, \(\text {ML}_\text {CoDa}\) is implemented as a standard .NET library. In the path towards the implementation a crucial role has been played by the formal description of the language and by its formal semantics, which highlight and explain how the two components of \(\text {ML}_\text {CoDa}\) interact. Furthermore they helped in identifying and describing the crucial parts of the implementation toolchain, compilation, generated code and runtime structures.

Here, we will survey on some applications we developed in \(\text {ML}_\text {CoDa}\) to assess our language, showing how context interactions can be better specified, analysed and controlled. We also discuss some extensions that will make our language more expressive and applicable. The next section introduces \(\text {ML}_\text {CoDa}\), with the help of our first case study. Two more case studies are summarised in Sect. 3. Section 4 shortly illustrates the Just-In-Time compiler of \(\text {ML}_\text {CoDa}\). In Sect. 5 we conclude and discuss the planned extensions, in particular those required to handle many applications running concurrently.

2 A First Example: An e-Healthcare System

Here, we illustrate the main features of \(\text {ML}_\text {CoDa}\) by considering an e-healthcare system with a few aspects typical of the Internet of Things. A more detailed description of this case study is in [7], and its full executable definition is in https://github.com/vslab/fscoda.

In our scenario each physician can retrieve a patient’s clinical record using a smartphone or a tablet, which also tracks the current location. Got the relevant data, the doctor decides which exams the patient needs and the system helps scheduling them. In addition, the system checks whether the doctor has the competence and the permission to actually perform the required exam, otherwise it suggests another physician who is enabled to, possibly coming from another department. Moving from a ward to another, the operating context changes and allows the doctor to access the complete clinical records of the patients therein. The application must adapt to the new context and it may additionally provide different features, e.g. by disabling rights to use some equipment and by acquiring access to new ones. Indeed, location-awareness of devices is exploited to tune access policies.

The e-healthcare context. We consider below a small part of the context, in particular that for storing and making some data available about the doctors’ location, information on their devices, the patients’ records and the ward medical equipment. Some basic data are represented by Datalog facts, and one can retrieve further information using the inference machinery of Datalog, which uses logical rules, also stored in the context.

For example, the fact that Dr. Turk is in the cardiology ward is rendered as

figure a

The following inference rule permits to deduce that the clinical data of patients can be accessed by the doctors in the same department where patients are. It states that the predicate on the left hand-side of the implication operator holds when the conjunction of the predicates ( and ) in the right hand-side yields true, i.e. when the physician and patient’s location coincide.

figure b

The \(\text {ML}_\text {CoDa}\) context is quite expressive and can model fairly complex situations. Typically, some medical exams can only be performed after some others. To compute this list of exams, all the dependencies among them are to be considered. This could be expressed by the following recursive rules:

figure c

The first rule means that the prescription of an exam implies that the involved patient needs the results of the test. The second rule says that whenever a patient needs an exam, so are also needed all the screenings the exam depends on. Datalog can conveniently model recursive relations like the dependency among exams, which may require involved queries with standard relational databases.

The next rule dictates that a patient has to do an exam if the two clauses in the right hand-side are true. The first has been already discussed above, while the second clause says that a patient should not do an exam if its results are already known (in the rule below the operator denotes the logical not, dealt with in our version of Datalog [8]).

figure d

In addition, we can declaratively describe physical objects in quite a similar, homogeneous manner. The following (simplified) rule specifies when a device can display a certain exam, by checking whether it has the needed capabilities:

figure e

By listing a set of facts, we can easily assert the capabilities of a device, e.g.

figure f

Adaptation constructs. Now we focus on context-dependent bindings and behavioural variations. These adaptation constructs allow specifying program behaviour, which depends on the context in our e-healthcare system. When entering a ward, the patients’ records under treatment can be displayed on the doctor’s personal device. Moreover, the e-healthcare system computes the list of the clinical exams a patient should do and that the doctor can perform. The following code (in a F#-like syntax) shows how the adaptation constructs are used to implement these functionalities. The function, given a doctor and a patient , prints the information about the patient’s exams on the screen.

figure g

Behavioural variations change the program flow according to the current context. They have the form , where the sub-expression explicitly refers to the context; the part introduces the goal to solve; and is the sub-expression to evaluate when the goal is true.

Using the outermost behavioural variation (starting at line 2), we check whether the doctor is allowed to access the data of the patient , when the goal at line 3 holds.

With the nested behavioural variation (line 4), we check if the patient has got the results of some exams, using the predicate . If this is the case, the construct extracts the list of exam results from the context (line 7). The statement iterates the evaluation of over all the solutions of the . It works as an iterator on-the-fly, driven by the solvability of the goal in the context. The predicate at line 7 contains the goal variable : if the query succeeds, at each iteration is bound to the current value satisfying . A goal variable is introduced in a goal, defining its scope, using the syntax .

Finally, through (the context dependent binding), the function shows an exam that the physician can do on the patient . At lines 12–13 we declare by cases the parameter , referred to in line 16. Only at run time when the actual context is known, we can determine which case applies and which value will be bound to when the parameter is used. If the goal in lines 14–15 holds, then assumes the value retrieved from the context, otherwise it gets the default value .

Note that it may happen that no goal is satisfied in a context while executing a behavioural variation or resolving a parameter. This means that the application in not able to adapt, either because the programmer assumed at design time the presence of functionalities that the current context lacks, or because of design errors. We classify this new kind of runtime errors as adaptation failures. For example, the following function assumes that given the identifier of a physician, it is always possible to retrieve the physician’s location from the context using the predicate:

figure h

The context-dependent binding may find no solution for the goal, e.g. when is invoked on a physician whose location is not in the context. If this is the case, the current implementation throws a runtime exception.

figure i

As described in [11], we may adopt a more sophisticated approach where for statically determining whether the adaptation might fail and reporting it before running the application.

Finally, the interaction with the Datalog context is not limited to queries: it is possible indeed to program the modifications to the knowledge base on which it performs deduction, by adding or removing facts with the and operations, as in:

figure j

Some execution examples. We now show how the functions defined above give different results when invoked in different contexts, parts of which are only described intuitively. For instance, in a context where Dr. Turk is not in the same ward as Bob, the result of the invocation is . This is because physicians are only allowed to see data about the patients in the department where they are. Indeed, the behavioural variation introduced at line 3 on finds out that accessing data is not allowed. If instead Dr. Cox is in the same department where Bob is, the call correctly prints the details about Bob (actually stored in the Datalog knowledge base):

figure k

In this case the outermost behavioural variation (starting at line 2) confirms that Dr. Cox can view the data. The nested one (starting at line 4), driven by , finds no exam for Bob, hence the function displays the no-exam message (line 10). Furthermore, the program finds out that Dr. Cox could do a blood test on Bob, as he is enabled to; then it additionally finds out that Bob needs no pre-screening and so that exam can be done immediately, because the predicate at line 15 holds.

Suppose now to have a slightly more complex situation, in which the context itself is modified. Patient Jordan has already performed an EEG test, and doctors prescribed her a CT and nothing else. Dr. Kelso is in Jordan’s room, is enabled to do only CT tests and carries a device on which he can visualise the results. In this context, the invocation outputs

figure l

Differently from the case above, Jordan has already performed an exam, listed by the iteration construct. Once Dr. Kelso have performed a CT scan on Jordan, the context has to be accordingly changed, by asserting the fact

figure m

Now the query has a different output in the modified context: besides displaying a longer list of exam results, the application shows Dr. Kelso that Jordan needs him to perform no other exam:

figure n

Suppose now that Dr. Cox moves to Jordan room and checks her medical report, but he has a device that cannot show CT images. The function warns the doctor and possibly presents the results in a more limited form, e.g. a static thumbnail. So, the result of the query is

figure o

3 Further Case Studies

The following case studies illustrate how \(\text {ML}_\text {CoDa}\) can be used to specify small-sized real context-aware applications. Afterwards, we outline some internals of our preliminary compiler.

Fsc-Rover. We now briefly describe the implementation in \(\text {ML}_\text {CoDa}\) of a small rover robot, endowed with two wheels, engine control, foto and video camera and a distance sensor, done by Riccardo Rolla, a master student of our research group (see https://github.com/riccardorolla/rpi-iot-fscoda). The rover moves in a building, and detects the objects therein and some of their features. Also, it interacts with other applications that use the information it collects, by exchanging messages on the Internet. The rover can perform either its actions, called local, or actions issued by other applications, called remote. Each kind of action has a different set of parameters and the rover has to identify the right values for them, by inspecting the properties of the objects in the context.

Besides getting a formal executable specification of a rover, differently from the other case studies this one makes it evident that the context provides effective support to uniformly handle both local and remote activities. The control loop of the rover, shown below, is really quite standard: it repeats the following until no-request is found

  • Add to the rover program all the remote actions read from the context;

  • Execute asynchronously local and remote actions;

  • Collect and process data and store the results in the context;

  • Send responses to remote applications.

figure p

The query request(ctx?idchat,ctx?cmd) extracts information from the context to assemble the list of commands to be executed. This is done by checking for messages arriving at the context from the Internet. The tag idchat identifies a remote application. Note that both rover commands and results are modelled as suitable facts inside the context through the and operations. The function run sets up the context, e.g. it turns on/off the video camera and the distance sensor. Its code not displayed here also invokes a configuration function that sets the sequence of local actions.

The behaviour of the rover also depends on the obstacles identified by the camera in the current environment. The following function is used to detect the nature of the obstacles by inspecting the context. The idea is that the objects are suitable facts in the context and that object recognition in the current image depends on the parameters of confidence of objects, such as size, rotation, etc.

figure q

FSEdit editor. Here, we briefly survey the implementation of FSEdit, a context-aware text editor implemented in \(\text {ML}_\text {CoDa}\). This case study was a workbench for testing how our implementation deals and interacts with pure F# code, in particular against the standard GUI library provided by .NET. Besides playing with contexts, this case study also helped finding some little flaws in the way our compiler treated some pieces of code using the object oriented features of F#. Furthermore, it also allowed us to identify some programming patterns that may be considered as idiomatic of \(\text {ML}_\text {CoDa}\) programs (see below).

The editor supports three different execution modes: rich text editor, text editor and programming editor. A context switch among the different modes changes the GUI of the editor, by offering e.g. different tool-bars and menus.

In the first mode, the GUI allows the user to set the size and the face of a font; to change the color of text; and to adjust the alignment of the paragraphs. In the second mode, the editor becomes very minimalistic and allows the user to edit pure text files, where no information of the text formatting can change. Finally, in the programming mode, the editor shows file line numbers and provides a simple form of syntax highlighting for C source files.

The context of FSEdit contains the current execution mode and other information that directly depend on it, as shown by the predicates below:

figure r

For example, the predicate only holds in the programming mode and returns the keywords of the programming language selected by the user to perform syntax highlighting. For simplicity, the editor currently supports the C programming language only. The second piece of information is about the kind of files supported by the editor in the different modes. For instance, *.rtf files in rich text mode, *.txt files in text mode and *.c in programming mode.

As said before, the execution mode affects the behaviour of the editor. For instance, in the following piece of code we invoke the syntax highlighter procedure when the user changes the text, if the editor is in the right mode.

figure s

As anticipated, this code snippet is interesting because it shows an idiomatic use of the context dependent binding. Indeed, there are two definitions of the identifier : the first one represents the basic behaviour of the editor that is independent of the context; the second one extends the basic behaviour with the features that are to be provided when the editor is in the programming mode. Notice in particular the use of on the right-hand side in the last-but-one line of the snippet. Although it may seem a recursive definition it is not; it is instead an invocation of defined in the previous line, i.e. the one specifying the basic behaviour of the editor.

4 A Glimpse on \(\text {ML}_\text {CoDa}\) Compiler

The \(\text {ML}_\text {CoDa}\) compiler ypc is based on the integration of the functional language F# with a customised version of YieldPrologFootnote 1 serving as Datalog engine.

Our compiler ahead-of-time compiles each Datalog predicate into a .NET method, whose code enumerates one by one the solutions, i.e. the assignments of values to variables that satisfy the predicate. In this way, the interaction and the data exchange between the application and the context is fully transparent to the programmer because the .NET type system is uniformly used everywhere.

The functional part of \(\text {ML}_\text {CoDa}\) that extends F# is implemented through just-in-time compilation. To do that, a programmer annotates these extensions with custom attributes, among which the most important is CoDa.Code. When a function annotated by it is to be executed, the \(\text {ML}_\text {CoDa}\) runtime is invoked to trigger the compilation step. Since the operations needed to adapt the application to contexts are transparently handled by our runtime support, the compiler fsharpc works as it is. Actually, CoDa.Code is an alias for the standard ReflectedDefinitionAttribute that marks modules and members whose abstract syntax trees are used at runtime through reflection. Of course \(\text {ML}_\text {CoDa}\) specific operations are only allowed in methods marked with this attribute; otherwise an exception is raised when they are invoked.

5 Conclusions, Discussion and Open Problems

We have surveyed the COP language \(\text {ML}_\text {CoDa}\) and we have reported on the experiments carried on some case studies. These proved \(\text {ML}_\text {CoDa}\) expressive enough to support the designer of real applications, although admittedly simplified in some details. The formal description of the dynamic and the static semantics of \(\text {ML}_\text {CoDa}\) drove a preliminary implementation of a compiler and of an analysis tool. Especially, we found that the bipartite nature of \(\text {ML}_\text {CoDa}\) permits the designer to clearly separate the design of the context from that of the application, yet maintaining their inter-relationships. This is particularly evident in the rover case study of Sect. 3, where the context provides the mechanism to virtualise and abstract from the communication infrastructure, thus making the logic of the control of the rover fully independent from the actual features of the communication infrastructure.

At the same time, the people working with \(\text {ML}_\text {CoDa}\) asked for more functionalities to make \(\text {ML}_\text {CoDa}\) more effective, and below we discuss some lines of improvement, both pragmatic and theoretical.

Non-functional properties. A crucial aspect that arose when designing the e-healthcare system concerns context-aware security and privacy, which we approached still from a formal linguistic viewpoint. We equipped \(\text {ML}_\text {CoDa}\) with security policies and with mechanisms for checking and enforcing them [4]. It turns out that policies are just Datalog clauses and that enforcing them reduces to asking goals. As a matter of fact, the control of safety properties, like access control or other security policies, requires extensions to the knowledge base and to its management that are not too heavy. We also extended the static analysis mentioned above to identify the operations that may violate the security policies in force. Recall that this step can only be done at load time, because the execution context is only known when the application is about to run, and thus our static analysis cannot be completed at compile time. Yet we have been able to instrument the code of an application, so as to incorporate in it an adaptive reference monitor, ready to stop executions when a policy to be enforced is about to be violated. When an application enters a new context, the results of the static analysis mentioned above are used to suitably drive the invocation of the monitor that is switched on and off upon need.

Further work will investigate other non-functional properties that however are of interest in real applications. A typical example is quality of service, which requires enriching both our logical knowledge base and our applications with quantitative information, in primis time. Such an extension would also provide the basis for evaluating both applications and contexts. For instance, statistical information about performance can help in choosing the application that better fits our needs, as well as statistical information on the usage of contexts or reliability of resources therein can be used for suggesting the user the context that guarantees more performance. A further approach to statically reason about resource usage, typically acquisition and release, is in [5].

Coherency of the context and interference. Other issues concern the context, in particular the operations to handle it and to keep it coherent. When developing and testing the e-healthcare system discussed in Sect. 2, it was necessary to extend the Datalog deduction machinery in order to get the entire list of the solutions to a given query.

Pursuing coherency at any cost can instead hinder adaptation, e.g. when an application e can complete its task even in a context that became partially incoherent. This is a pragmatically very relevant issue. Consider for example the case when a resource becomes unavailable, but was usable by e in the context when a specific behavioural variation started. At the moment we implemented our language in a strict way that prevents e even to start executing. For sure this guarantees that no troubles will arise, but also precludes to run an application that only uses that resource when available, e.g. at the very beginning, and never again, so no adaptation error will show up at run time. While a continuous run time monitoring can handle this problem, but at a high cost, finding a sound and efficient solution to this issue is a hard challenge from a theoretical point of view. Indeed, it involves a careful mix of static analysis and of run time monitoring of the applications that are executing in a context. Also, “living in an incoherent context” is tightly connected with the way one deals with the needed recovery mechanisms that should be activated without involving the users.

Concurrency. The above problem is critical in the inherently concurrent systems we are studying. Indeed, an application does not perform its task in isolation, rather it needs some resources offered by a context where plenty of other applications are running therein (often competing for those resources). For instance, the implementation of the rover described in Sect. 3 posed concurrency issues, because the control activity of the robot is performed in parallel with the collection and analysis of data coming from the context. The current ad hoc solution exploits the management of threads offered by the operative system, and it is not yet fully integrated in \(\text {ML}_\text {CoDa}\).

A first extension of \(\text {ML}_\text {CoDa}\) with concurrency is in [12], where there is a two-threaded system: the context and the application. The first virtualises the resources and the communication infrastructure, as well as other software components running within it. Consequently, the behaviour of a context, describing in particular how it is updated, abstractly accounts for all the interactions of the entities it hosts. The other thread is the application and the interactions with the other entities therein are rendered as the occurrence of asynchronous events that represent the relevant changes in the context. A more faithful description of concurrency requires to explicitly describing the many applications that execute in a context, that exchange information using it and that asynchronously update it. This is the approach followed in [6]. Nonetheless, the well known problem of interference now arises, because one thread can update the context possibly making unavailable some resources or contradicting assumptions that another thread relies upon. Classical techniques for controlling this form of misbehaviour, like locks, are not satisfying, because they contrast with the basic assumption of having an open world where applications appear and disappear unpredictably, and freely update the context. However, application designers are only aware of the relevant fragments of the context and cannot anticipate the effects a change may have. Therefore, the overall consistency of the context cannot be controlled by applications, and “living in an incoherent context” is unavoidable. The semantics proposed in [6] addresses this problem using a run time verification mechanism. Intuitively, the effects of the running applications are checked to guarantee that the execution of the selected behavioural variation will lead no other application to an inconsistent state, e.g. by disposing a shared resource. Dually, also the other threads are checked to verify that they are harmless with respect to the application entering in a behavioural variation.

Recovery mechanisms. We already mentioned briefly the need of recovery mechanism when run time errors arise, in particular when adaptation failures prevent an application to complete its task. Recovery mechanisms are especially needed to adapt applications that raise security failures, in case of policy violations. Recovery should be carried on with little or no user involvement, and this imposes on the system running the applications to execute parts of their code “atomically.” A typical way is to consider those pieces of code as all-or-nothing transactions, and to store auxiliary information for recovering from failures. If the entire transaction is successfully executed, then the auxiliary information can be disposed, otherwise it has to be used to restore the application in a consistent state, e.g. the one holding at the start of the transaction. To this end, we plan to investigate recovery mechanisms appropriate for behavioural variations, to allow the user to undo some actions considered risky or sensible, and force the dispatching mechanism to make different, alternative choices.

However, in our world the context might have been changed in the meanwhile, and such a state might not be consistent any longer. A deep analysis is therefore needed of the interplay between the way applications use contextual information to adapt or to execute, and the highly dynamic way in which contexts change. A possible line of investigation can be giving up with the quest for a coherent global context, while keeping coherent portions of it, i.e. local contexts where applications run and, so to speak, posses for a while.