Join GitHub today
Clojure is an alternative role and environment implementation language to native Java in µ².
In order to use it a basic knowledge of Clojure is assumed (Clojure syntax, namespace principles, transactional memory, Java interop). Primary resource for this is the Clojure website. A helpful overview on numerous further Clojure-related resources is provided here.
Clojure implementations are used as clj scripts. In order to use them an according folder structure needs to be set up. In fact the folder structure is provided with the distribution download of µ². Its details are described at this point.
The folder structure can be freely configured via the ClojureConnector which is responsible for all Clojure-related configuration issues. However, in the default implementation the layout is as follows:
cljScripts - folder residing in user's current working directory (of the Java application) respectively Eclipse project (identified by the Java system property user.dir via System.getProperty("user.dir")). It has the following subfolders:
- appCljScripts - folder containing application-dependent clj scripts (e.g. containing an application environment). Folder configuration can be changed using ClojureConnector.setApplicationScriptPath(). The parameter needs to contain the relative path from the working directory (respectively Eclipse project root folder) (e.g. "cljScripts/applicationScripts").
- commonCljScripts - folder containing scripts with functionality common in all agents. Scripts in this follow capture platform functions accessible to each role implementation (such as (send-msg), ...). Folder configuration can be changed using ClojureConnector.setCommonScriptPath(). The parameter needs to contain the relative path from the working directory (respectively Eclipse project root folder) (e.g. "cljScripts/commonScripts").
- indivCljScripts - folder containing individual clj scripts which could be run on individual agents (not roles!). Those typically implement full agent capability, not only one role. Folder configuration can be changed using ClojureConnector.setIndividualScriptPath(). The parameter needs to contain the relative path from the working directory (respectively Eclipse project root folder) (e.g. "cljScripts/individualScripts").
- roleCljScripts - folder containing role implementation scripts. This directory is most likely to be used when implementation Clojure functionality in µ² as the general paradigm is to implement roles rather than agents (though possible; agent implementations in Clojure are saved to the individual script path (indivCljScripts)). Folder configuration can be changed using ClojureConnector.setRoleScriptPath(). The parameter needs to contain the relative path from the working directory (respectively Eclipse project root folder) (e.g. "cljScripts/roleScripts").
For small implementations it might make sense to condense the folder structure into fewer script folders (e.g. pointing all paths to the same disk directory). However, when dealing with more complex setups (and considering the some scripts are rather system-related than application-dependent (e.g. commonCljScripts)) a more extensive structure is useful. For the distribution of programs the above-mentioned structure is suggested (but eventually omitting unused folders).
Along with this several files are predefined and delivered with each distribution. Those include (with folder they are located in):
- cljScripts/commonCljScripts/platform.clj - Platform script for the whole platform. It can be changed using setPlatformScript(<scriptname.clj>).
- cljScripts/commonCljScripts/commonAgentBase.clj - Agent code used in each Clojure implementation
Further the user can freely specify an application script (which needs to reside in the appCljScripts path) via setApplicationScript(<scriptname.clj>) which represents the application environment for all agents as well as a post-initialization script using setPostInitScript(<scriptname.clj>) to run code which needs to be run after initialization of all individual agents but before the actual application is run.
The configuration of the above-mentioned paths is printed in the console when the Clojure component is started which can be helpful for validating path setups.
Clojure role implementation
Role implementations in Clojure merely demand for the implementation of a (receive-msg [MicroMessage]) function which represents the handleMessage() method from the Java implementation. Implemented scripts are saved into a the roleCljScripts path. To initialize the CLJ script on an agent the DefaultCljSocialRole (which itself extends the SocialRole) needs to be extended. It demands for a script name (which is the script saved in the roleCljScript folder (e.g. "exampleRole.clj")). However, often variables in Clojure need to be initialized upon role initialization. This can be achieved in the initializeClj() method of DefaultCljSocialRole running Clojure code held in a StringBuffer and executed via getAgent().execClj() in the Clojure environment.
The code below shows a simple example setting a variable called round to 1 upon initialization.
The according clj script file "ExampleRole.clj" in the roleCljScript folder is shown below. It prints a received message in the console and sends a response message to the sender containing the text "RECEIVED" in the performative field (to show the flexibility of message fields usage).
The initialization code is shown in the code snippet below.
In order to run the script it Clojure needs to be activated (as either done in the configuration file or in-code as shown in the code snippet). After initializing the agents the Clojure console REPL can be initialized to allow interactive introspection into agents respectively roles.
As all Clojure instances share the same JVM memory (and as such in theory can directly access other agent's memory) agents implemented in Clojure are separated by their namespaces. The general namespace is the namespace user. Applications might further define specific namespaces. The namespace for individual agents is set up according to the pattern user.ns (with as registered agent name). More in-depth examples (including tutorial-like screenshots) for this interactive introspection is given in the context of the Cultural Dimensions Simulation. This application also shows a comprehensive role implementation using Clojure (and further exploits the feature of Clojure method invocation from Java (see behave function)) and is a useful reference for more detailed implementation concerns than discussed here.
An example for an application making use of Clojure as environment implementation language is the Talking Ants Application. Its agents are implemented in Java but the environment is fully held in Clojure which represents the alternative use of Clojure in µ². Key difference in Clojure-based environment implementations is that individual agents hold reference to the environment namespace - and as such can access and modify it concurrently. For detailed introduction for its use please refer to the provided code.
The ClojureConnector allows a more comprehensive management of the Clojure component of the platform.
The ClojureConnector is typically started lazy but can be enforced using startPlatform(). Further functions include the running of the post-initialization script (which needs to be specified using setPostInitScript() (see above)) after all other functionality has been initialized (runPostInitScript()). Last but not least to mention are startConsoleREPL() (to start a console-based REPL) and startNetworkREPL() (which optionally allows the specification of a network port) to connect to the REPL via network.
Please be aware that after starting the console-based REPL no further Java commands will be executed. So it must be started last in the application main method.